use std::time::Duration;
fn should_update_progress(current_file: usize, elapsed_since_update: Duration) -> bool {
current_file % 10 == 0 || elapsed_since_update > Duration::from_millis(100)
}
fn simulate_progress_updates(
total_files: usize,
elapsed_per_file: impl Fn(usize) -> Duration,
include_final_update: bool,
) -> Vec<(usize, usize)> {
let mut progress_calls = Vec::new();
let mut elapsed_since_update = Duration::ZERO;
for idx in 0..total_files {
let current_file = idx + 1;
elapsed_since_update += elapsed_per_file(idx);
if should_update_progress(current_file, elapsed_since_update) {
progress_calls.push((current_file, total_files));
elapsed_since_update = Duration::ZERO;
}
}
if include_final_update {
progress_calls.push((total_files, total_files));
}
progress_calls
}
#[test]
fn test_progress_throttling_every_10_files() {
let total_files = 25;
let progress_calls =
simulate_progress_updates(total_files, |_| Duration::from_micros(10), false);
let final_count = progress_calls.len();
assert!(
final_count == 2,
"Expected 2 updates with throttling, got {}",
final_count
);
assert!(
final_count < total_files,
"Throttling should reduce updates from {} to {}",
total_files,
final_count
);
}
#[test]
fn test_progress_values_are_monotonic() {
let total_files = 15;
let progress_values = simulate_progress_updates(total_files, |_| Duration::ZERO, true);
for (current, total) in &progress_values {
assert_eq!(
*total, total_files,
"Total should always be {}",
total_files
);
assert!(*current <= total_files, "Current should never exceed total");
assert!(*current > 0, "Current should be positive");
}
for i in 1..progress_values.len() {
assert!(
progress_values[i].0 >= progress_values[i - 1].0,
"Progress should be monotonically increasing"
);
}
}
#[test]
fn test_throttling_with_time_based_updates() {
let total_files = 50;
let progress_calls = simulate_progress_updates(
total_files,
|idx| {
if idx % 5 == 0 {
Duration::from_millis(25)
} else {
Duration::ZERO
}
},
false,
);
let final_count = progress_calls.len();
assert!(
final_count >= 5,
"Expected at least 5 updates, got {}",
final_count
);
assert!(
final_count < total_files,
"Throttling should limit updates to less than total files"
);
}
#[test]
fn test_progress_callback_invocation_pattern() {
let total_files = 25;
let progress_calls = simulate_progress_updates(total_files, |_| Duration::ZERO, true);
assert!(!progress_calls.is_empty(), "Should have progress updates");
let final_call = progress_calls.last().unwrap();
assert_eq!(final_call.0, total_files, "Final current should be total");
assert_eq!(final_call.1, total_files, "Final total should be total");
for (current, total) in &progress_calls {
assert_eq!(*total, total_files);
assert!(*current > 0 && *current <= total_files);
}
}
#[test]
fn test_throttling_prevents_excessive_updates() {
let total_files = 100;
let progress_calls = simulate_progress_updates(total_files, |_| Duration::ZERO, false);
let final_count = progress_calls.len();
assert!(
final_count == 10,
"Expected 10 updates for 100 files, got {}",
final_count
);
let throttle_percentage = (1.0 - (final_count as f64 / total_files as f64)) * 100.0;
assert!(
throttle_percentage >= 80.0,
"Should throttle by at least 80%, got {}%",
throttle_percentage
);
}
#[test]
fn test_time_based_throttling_100ms() {
let total_files = 5;
let mut elapsed_since_update = Duration::ZERO;
let mut update_times = Vec::new();
for idx in 0..total_files {
elapsed_since_update += Duration::from_millis(30);
if should_update_progress(idx + 1, elapsed_since_update) {
update_times.push(elapsed_since_update);
elapsed_since_update = Duration::ZERO;
}
}
assert!(
!update_times.is_empty(),
"Should have time-based updates after 150ms"
);
for elapsed in &update_times {
assert!(
*elapsed >= Duration::from_millis(100),
"Update interval should be >= 100ms, got {:?}",
elapsed
);
}
}