Merge pull request #427 from rustfs/feat/improve-common-module-tests

feat: add comprehensive test coverage for common module
This commit is contained in:
安正超
2025-05-27 23:03:16 +08:00
committed by GitHub
2 changed files with 691 additions and 0 deletions
+244
View File
@@ -89,3 +89,247 @@ impl std::fmt::Display for Error {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[derive(Debug)]
struct CustomTestError {
message: String,
}
impl std::fmt::Display for CustomTestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Custom test error: {}", self.message)
}
}
impl std::error::Error for CustomTestError {}
#[derive(Debug)]
struct AnotherTestError;
impl std::fmt::Display for AnotherTestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Another test error")
}
}
impl std::error::Error for AnotherTestError {}
#[test]
fn test_error_new_from_std_error() {
let io_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
let error = Error::new(io_error);
assert!(error.inner_string().contains("File not found"));
assert!(error.is::<io::Error>());
}
#[test]
fn test_error_from_std_error() {
let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "Permission denied");
let boxed_error: StdError = Box::new(io_error);
let error = Error::from_std_error(boxed_error);
assert!(error.inner_string().contains("Permission denied"));
assert!(error.is::<io::Error>());
}
#[test]
fn test_error_from_string() {
let error = Error::from_string("Test error message");
assert_eq!(error.inner_string(), "Test error message");
}
#[test]
fn test_error_msg() {
let error = Error::msg("Another test message");
assert_eq!(error.inner_string(), "Another test message");
}
#[test]
fn test_error_msg_with_string() {
let message = String::from("String message");
let error = Error::msg(message);
assert_eq!(error.inner_string(), "String message");
}
#[test]
fn test_error_is_type_checking() {
let io_error = io::Error::new(io::ErrorKind::InvalidInput, "Invalid input");
let error = Error::new(io_error);
assert!(error.is::<io::Error>());
assert!(!error.is::<CustomTestError>());
}
#[test]
fn test_error_downcast_ref() {
let io_error = io::Error::new(io::ErrorKind::TimedOut, "Operation timed out");
let error = Error::new(io_error);
let downcast_io = error.downcast_ref::<io::Error>();
assert!(downcast_io.is_some());
assert_eq!(downcast_io.unwrap().kind(), io::ErrorKind::TimedOut);
let downcast_custom = error.downcast_ref::<CustomTestError>();
assert!(downcast_custom.is_none());
}
#[test]
fn test_error_downcast_mut() {
let io_error = io::Error::new(io::ErrorKind::Interrupted, "Operation interrupted");
let mut error = Error::new(io_error);
let downcast_io = error.downcast_mut::<io::Error>();
assert!(downcast_io.is_some());
assert_eq!(downcast_io.unwrap().kind(), io::ErrorKind::Interrupted);
let downcast_custom = error.downcast_mut::<CustomTestError>();
assert!(downcast_custom.is_none());
}
#[test]
fn test_error_to_io_err() {
// Test with IO error
let original_io_error = io::Error::new(io::ErrorKind::BrokenPipe, "Broken pipe");
let error = Error::new(original_io_error);
let converted_io_error = error.to_io_err();
assert!(converted_io_error.is_some());
let io_err = converted_io_error.unwrap();
assert_eq!(io_err.kind(), io::ErrorKind::BrokenPipe);
assert!(io_err.to_string().contains("Broken pipe"));
// Test with non-IO error
let custom_error = CustomTestError {
message: "Not an IO error".to_string(),
};
let error = Error::new(custom_error);
let converted_io_error = error.to_io_err();
assert!(converted_io_error.is_none());
}
#[test]
fn test_error_inner_string() {
let custom_error = CustomTestError {
message: "Test message".to_string(),
};
let error = Error::new(custom_error);
assert_eq!(error.inner_string(), "Custom test error: Test message");
}
#[test]
fn test_error_from_trait() {
let io_error = io::Error::new(io::ErrorKind::UnexpectedEof, "Unexpected EOF");
let error: Error = io_error.into();
assert!(error.is::<io::Error>());
assert!(error.inner_string().contains("Unexpected EOF"));
}
#[test]
fn test_error_display() {
let custom_error = CustomTestError {
message: "Display test".to_string(),
};
let error = Error::new(custom_error);
let display_string = format!("{}", error);
assert!(display_string.contains("Custom test error: Display test"));
}
#[test]
fn test_error_debug() {
let error = Error::msg("Debug test");
let debug_string = format!("{:?}", error);
assert!(debug_string.contains("Error"));
assert!(debug_string.contains("inner"));
assert!(debug_string.contains("span_trace"));
}
#[test]
fn test_multiple_error_types() {
let errors = vec![
Error::new(io::Error::new(io::ErrorKind::NotFound, "Not found")),
Error::new(CustomTestError { message: "Custom".to_string() }),
Error::new(AnotherTestError),
Error::msg("String error"),
];
assert!(errors[0].is::<io::Error>());
assert!(errors[1].is::<CustomTestError>());
assert!(errors[2].is::<AnotherTestError>());
assert!(!errors[3].is::<io::Error>());
}
#[test]
fn test_error_chain_compatibility() {
// Test that our Error type works well with error chains
let io_error = io::Error::new(io::ErrorKind::InvalidData, "Invalid data");
let error = Error::new(io_error);
// Should be able to convert back to Result
let result: Result<(), Error> = Err(error);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is::<io::Error>());
}
#[test]
fn test_result_type_alias() {
// Test the Result type alias
fn test_function() -> Result<String> {
Ok("Success".to_string())
}
fn test_function_with_error() -> Result<String> {
Err(Error::msg("Test error"))
}
let success_result = test_function();
assert!(success_result.is_ok());
assert_eq!(success_result.unwrap(), "Success");
let error_result = test_function_with_error();
assert!(error_result.is_err());
assert_eq!(error_result.unwrap_err().inner_string(), "Test error");
}
#[test]
fn test_error_with_empty_message() {
let error = Error::msg("");
assert_eq!(error.inner_string(), "");
}
#[test]
fn test_error_with_unicode_message() {
let unicode_message = "错误信息 🚨 Error message with émojis and ñon-ASCII";
let error = Error::msg(unicode_message);
assert_eq!(error.inner_string(), unicode_message);
}
#[test]
fn test_error_with_very_long_message() {
let long_message = "A".repeat(10000);
let error = Error::msg(&long_message);
assert_eq!(error.inner_string(), long_message);
}
#[test]
fn test_span_trace_capture() {
// Test that span trace is captured (though we can't easily test the content)
let error = Error::msg("Span trace test");
let display_string = format!("{}", error);
// The error should at least contain the message
assert!(display_string.contains("Span trace test"));
}
}
+447
View File
@@ -110,3 +110,450 @@ impl LastMinuteLatency {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_acc_elem_default() {
let elem = AccElem::default();
assert_eq!(elem.total, 0);
assert_eq!(elem.size, 0);
assert_eq!(elem.n, 0);
}
#[test]
fn test_acc_elem_add() {
let mut elem = AccElem::default();
// Add first duration
let dur1 = Duration::from_secs(5);
elem.add(&dur1);
assert_eq!(elem.total, 5);
assert_eq!(elem.n, 1);
assert_eq!(elem.size, 0); // size is not modified by add
// Add second duration
let dur2 = Duration::from_secs(10);
elem.add(&dur2);
assert_eq!(elem.total, 15);
assert_eq!(elem.n, 2);
}
#[test]
fn test_acc_elem_add_with_subsecond_duration() {
let mut elem = AccElem::default();
// Add duration less than 1 second (should be truncated to 0)
let dur = Duration::from_millis(500);
elem.add(&dur);
assert_eq!(elem.total, 0);
assert_eq!(elem.n, 1);
}
#[test]
fn test_acc_elem_merge() {
let mut elem1 = AccElem {
total: 10,
size: 100,
n: 2,
};
let elem2 = AccElem {
total: 20,
size: 200,
n: 3,
};
elem1.merge(&elem2);
assert_eq!(elem1.total, 30);
assert_eq!(elem1.size, 300);
assert_eq!(elem1.n, 5);
}
#[test]
fn test_acc_elem_merge_with_empty() {
let mut elem = AccElem {
total: 10,
size: 100,
n: 2,
};
let empty_elem = AccElem::default();
elem.merge(&empty_elem);
assert_eq!(elem.total, 10);
assert_eq!(elem.size, 100);
assert_eq!(elem.n, 2);
}
#[test]
fn test_acc_elem_avg() {
// Test with valid data
let elem = AccElem {
total: 15,
size: 0,
n: 3,
};
assert_eq!(elem.avg(), Duration::from_secs(5));
// Test with zero count
let elem_zero_n = AccElem {
total: 10,
size: 0,
n: 0,
};
assert_eq!(elem_zero_n.avg(), Duration::from_secs(0));
// Test with zero total
let elem_zero_total = AccElem {
total: 0,
size: 0,
n: 5,
};
assert_eq!(elem_zero_total.avg(), Duration::from_secs(0));
// Test with both zero
let elem_both_zero = AccElem::default();
assert_eq!(elem_both_zero.avg(), Duration::from_secs(0));
}
#[test]
fn test_acc_elem_avg_with_single_element() {
let elem = AccElem {
total: 7,
size: 0,
n: 1,
};
assert_eq!(elem.avg(), Duration::from_secs(7));
}
#[test]
fn test_last_minute_latency_default() {
let latency = LastMinuteLatency::default();
assert_eq!(latency.totals.len(), 60);
assert_eq!(latency.last_sec, 0);
// All elements should be default
for elem in &latency.totals {
assert_eq!(elem.total, 0);
assert_eq!(elem.size, 0);
assert_eq!(elem.n, 0);
}
}
#[test]
fn test_last_minute_latency_clone() {
let mut latency = LastMinuteLatency::default();
latency.last_sec = 12345;
latency.totals[0].total = 100;
let cloned = latency.clone();
assert_eq!(cloned.last_sec, 12345);
assert_eq!(cloned.totals[0].total, 100);
assert_eq!(cloned.totals.len(), 60);
}
#[test]
fn test_forward_to_same_time() {
let mut latency = LastMinuteLatency::default();
latency.last_sec = 100;
// Forward to same time should not change anything
latency.forward_to(100);
assert_eq!(latency.last_sec, 100);
// Forward to earlier time should not change anything
latency.forward_to(99);
assert_eq!(latency.last_sec, 100);
}
#[test]
fn test_forward_to_large_gap() {
let mut latency = LastMinuteLatency::default();
latency.last_sec = 100;
latency.totals[0].total = 999; // Set some data
// Forward by more than 60 seconds should reset all totals
latency.forward_to(200);
assert_eq!(latency.last_sec, 100); // last_sec is not updated in this case
// All totals should be reset
for elem in &latency.totals {
assert_eq!(elem.total, 0);
assert_eq!(elem.size, 0);
assert_eq!(elem.n, 0);
}
}
#[test]
fn test_forward_to_small_gap() {
let mut latency = LastMinuteLatency::default();
latency.last_sec = 100;
latency.totals[1].total = 999; // Set some data at index 1
// Forward by 2 seconds
latency.forward_to(102);
assert_eq!(latency.last_sec, 102);
// Index 1 should still have data
assert_eq!(latency.totals[1].total, 999);
// Indices that were cleared should be zero
assert_eq!(latency.totals[(101 % 60) as usize].total, 0);
assert_eq!(latency.totals[(102 % 60) as usize].total, 0);
}
#[test]
fn test_add_all() {
let mut latency = LastMinuteLatency::default();
let acc_elem = AccElem {
total: 50,
size: 1000,
n: 5,
};
let test_sec = 12345;
latency.add_all(test_sec, &acc_elem);
assert_eq!(latency.last_sec, test_sec);
let win_idx = (test_sec % 60) as usize;
assert_eq!(latency.totals[win_idx].total, 50);
assert_eq!(latency.totals[win_idx].size, 1000);
assert_eq!(latency.totals[win_idx].n, 5);
}
#[test]
fn test_add_all_multiple_times() {
let mut latency = LastMinuteLatency::default();
let acc_elem1 = AccElem {
total: 10,
size: 100,
n: 1,
};
let acc_elem2 = AccElem {
total: 20,
size: 200,
n: 2,
};
let test_sec = 12345;
latency.add_all(test_sec, &acc_elem1);
latency.add_all(test_sec, &acc_elem2);
let win_idx = (test_sec % 60) as usize;
assert_eq!(latency.totals[win_idx].total, 30);
assert_eq!(latency.totals[win_idx].size, 300);
assert_eq!(latency.totals[win_idx].n, 3);
}
#[test]
fn test_merge_with_same_last_sec() {
let mut latency1 = LastMinuteLatency::default();
let mut latency2 = LastMinuteLatency::default();
latency1.last_sec = 100;
latency2.last_sec = 100;
latency1.totals[0].total = 10;
latency1.totals[0].n = 1;
latency2.totals[0].total = 20;
latency2.totals[0].n = 2;
let merged = latency1.merge(&mut latency2);
assert_eq!(merged.last_sec, 100);
assert_eq!(merged.totals[0].total, 30);
assert_eq!(merged.totals[0].n, 3);
}
#[test]
fn test_merge_with_different_last_sec() {
let mut latency1 = LastMinuteLatency::default();
let mut latency2 = LastMinuteLatency::default();
latency1.last_sec = 100;
latency2.last_sec = 105;
latency1.totals[0].total = 10;
latency2.totals[5].total = 20;
let merged = latency1.merge(&mut latency2);
// Should use the later timestamp
assert_eq!(merged.last_sec, 105);
}
#[test]
fn test_merge_all_slots() {
let mut latency1 = LastMinuteLatency::default();
let mut latency2 = LastMinuteLatency::default();
// Fill all slots with different values
for i in 0..60 {
latency1.totals[i].total = i as u64;
latency1.totals[i].n = 1;
latency2.totals[i].total = (i * 2) as u64;
latency2.totals[i].n = 2;
}
let merged = latency1.merge(&mut latency2);
for i in 0..60 {
assert_eq!(merged.totals[i].total, (i + i * 2) as u64);
assert_eq!(merged.totals[i].n, 3);
}
}
#[test]
fn test_get_total_empty() {
let mut latency = LastMinuteLatency::default();
let total = latency.get_total();
assert_eq!(total.total, 0);
assert_eq!(total.size, 0);
assert_eq!(total.n, 0);
}
#[test]
fn test_get_total_with_data() {
let mut latency = LastMinuteLatency::default();
// Set a recent timestamp to avoid forward_to clearing data
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
latency.last_sec = current_time;
// Add data to multiple slots
latency.totals[0] = AccElem { total: 10, size: 100, n: 1 };
latency.totals[1] = AccElem { total: 20, size: 200, n: 2 };
latency.totals[59] = AccElem { total: 30, size: 300, n: 3 };
let total = latency.get_total();
assert_eq!(total.total, 60);
assert_eq!(total.size, 600);
assert_eq!(total.n, 6);
}
#[test]
fn test_window_index_calculation() {
// Test that window index calculation works correctly
let mut latency = LastMinuteLatency::default();
let acc_elem = AccElem {
total: 1,
size: 1,
n: 1,
};
// Test various timestamps
let test_cases = [
(0, 0),
(1, 1),
(59, 59),
(60, 0),
(61, 1),
(119, 59),
(120, 0),
];
for (timestamp, expected_idx) in test_cases {
let mut test_latency = LastMinuteLatency::default();
test_latency.add_all(timestamp, &acc_elem);
assert_eq!(test_latency.totals[expected_idx].n, 1,
"Failed for timestamp {} (expected index {})", timestamp, expected_idx);
}
}
#[test]
fn test_edge_case_boundary_conditions() {
let mut latency = LastMinuteLatency::default();
// Test boundary at 60 seconds
latency.last_sec = 59;
latency.forward_to(119); // Exactly 60 seconds later
// Should reset all totals
for elem in &latency.totals {
assert_eq!(elem.total, 0);
}
}
#[test]
fn test_concurrent_safety_simulation() {
// Simulate concurrent access patterns
let mut latency = LastMinuteLatency::default();
// Use current time to ensure data doesn't get cleared by get_total
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
// Simulate rapid additions within a 60-second window
for i in 0..1000 {
let acc_elem = AccElem {
total: (i % 10) + 1, // Ensure non-zero values
size: (i % 100) + 1,
n: 1,
};
// Keep all timestamps within the current minute window
latency.add_all(current_time - (i % 60), &acc_elem);
}
let total = latency.get_total();
assert!(total.n > 0, "Total count should be greater than 0");
assert!(total.total > 0, "Total time should be greater than 0");
}
#[test]
fn test_acc_elem_debug_format() {
let elem = AccElem {
total: 123,
size: 456,
n: 789,
};
let debug_str = format!("{:?}", elem);
assert!(debug_str.contains("123"));
assert!(debug_str.contains("456"));
assert!(debug_str.contains("789"));
}
#[test]
fn test_large_values() {
let mut elem = AccElem::default();
// Test with large duration values
let large_duration = Duration::from_secs(u64::MAX / 2);
elem.add(&large_duration);
assert_eq!(elem.total, u64::MAX / 2);
assert_eq!(elem.n, 1);
// Test average calculation with large values
let avg = elem.avg();
assert_eq!(avg, Duration::from_secs(u64::MAX / 2));
}
#[test]
fn test_zero_duration_handling() {
let mut elem = AccElem::default();
let zero_duration = Duration::from_secs(0);
elem.add(&zero_duration);
assert_eq!(elem.total, 0);
assert_eq!(elem.n, 1);
assert_eq!(elem.avg(), Duration::from_secs(0));
}
}