#[cfg(test)]
mod tests {
use std::{
collections::HashMap,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex,
},
thread::{self, sleep},
time::{Duration, Instant},
};
use chrono::{FixedOffset, Local, TimeZone, Utc};
use cron_tab::{Cron, CronError};
#[test]
fn test_add_fn_once() {
let mut cron = Cron::new(Utc);
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
let target_time = Utc::now() + chrono::Duration::seconds(2);
cron.add_fn_once(target_time, move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(1500));
assert_eq!(*counter.lock().unwrap(), 0, "Job should not have executed yet");
sleep(Duration::from_millis(1000));
assert_eq!(*counter.lock().unwrap(), 1, "Job should have executed once");
sleep(Duration::from_millis(2000));
assert_eq!(*counter.lock().unwrap(), 1, "Job should only execute once");
cron.stop();
}
#[test]
fn test_add_fn_after() {
let mut cron = Cron::new(Utc);
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
cron.add_fn_after(Duration::from_secs(2), move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(1500));
assert_eq!(*counter.lock().unwrap(), 0, "Job should not have executed yet");
sleep(Duration::from_millis(1000));
assert_eq!(*counter.lock().unwrap(), 1, "Job should have executed once");
sleep(Duration::from_millis(2000));
assert_eq!(*counter.lock().unwrap(), 1, "Job should only execute once");
cron.stop();
}
#[test]
fn test_multiple_one_time_jobs() {
let mut cron = Cron::new(Utc);
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
let counter2 = Arc::clone(&counter);
let counter3 = Arc::clone(&counter);
cron.add_fn_after(Duration::from_secs(1), move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.add_fn_after(Duration::from_secs(2), move || {
let mut value = counter2.lock().unwrap();
*value += 10;
})
.unwrap();
cron.add_fn_after(Duration::from_secs(3), move || {
let mut value = counter3.lock().unwrap();
*value += 100;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(1500));
assert_eq!(*counter.lock().unwrap(), 1, "First job should have executed");
sleep(Duration::from_millis(1000));
assert_eq!(*counter.lock().unwrap(), 11, "Second job should have executed");
sleep(Duration::from_millis(1000));
assert_eq!(*counter.lock().unwrap(), 111, "Third job should have executed");
sleep(Duration::from_millis(1000));
assert_eq!(*counter.lock().unwrap(), 111, "No more jobs should execute");
cron.stop();
}
#[test]
fn test_remove_one_time_job_before_execution() {
let mut cron = Cron::new(Utc);
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
let job_id = cron
.add_fn_after(Duration::from_secs(2), move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(500));
cron.remove(job_id);
sleep(Duration::from_millis(2000));
assert_eq!(*counter.lock().unwrap(), 0, "Removed job should not execute");
cron.stop();
}
#[test]
fn test_mix_recurring_and_one_time_jobs() {
let mut cron = Cron::new(Utc);
let recurring_counter = Arc::new(Mutex::new(0));
let recurring_counter1 = Arc::clone(&recurring_counter);
let once_counter = Arc::new(Mutex::new(0));
let once_counter1 = Arc::clone(&once_counter);
cron.add_fn("* * * * * * *", move || {
let mut value = recurring_counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.add_fn_after(Duration::from_secs(2), move || {
let mut value = once_counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(3500));
let recurring_count = *recurring_counter.lock().unwrap();
let once_count = *once_counter.lock().unwrap();
assert!(
recurring_count >= 2,
"Recurring job should execute multiple times, got {}",
recurring_count
);
assert_eq!(once_count, 1, "One-time job should execute exactly once");
cron.stop();
}
#[test]
fn test_duration_out_of_range_error() {
let mut cron = Cron::new(Utc);
let very_long_time = Duration::from_secs(u64::MAX);
let result = cron.add_fn_after(very_long_time, || {
println!("This should not execute");
});
assert!(result.is_err(), "Should fail with DurationOutOfRange");
match result {
Err(CronError::DurationOutOfRange) => {
}
Err(e) => panic!("Expected DurationOutOfRange, got {:?}", e),
Ok(_) => panic!("Should have returned an error"),
}
let normal_duration = Duration::from_secs(10);
let result = cron.add_fn_after(normal_duration, || {
println!("This will execute");
});
assert!(result.is_ok(), "Should succeed with normal duration");
}
#[test]
fn start_and_stop_cron() {
let local_tz = Local::from_offset(&FixedOffset::east_opt(7).unwrap());
let mut cron = Cron::new(local_tz);
cron.start();
cron.stop();
}
#[test]
fn add_job_before_start() {
let local_tz = Local::from_offset(&FixedOffset::east_opt(7).unwrap());
let mut cron = Cron::new(local_tz);
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
cron.add_fn("* * * * * *", move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
cron.start();
sleep(Duration::from_millis(2001));
let value = *counter.lock().unwrap();
assert!(value >= 1, "Expected at least 1 execution, got {}", value);
}
#[test]
fn add_job() {
let local_tz = Local::from_offset(&FixedOffset::east_opt(7).unwrap());
let mut cron = Cron::new(local_tz);
cron.start();
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
cron.add_fn("* * * * * *", move || {
let mut value = counter1.lock().unwrap();
*value += 1;
})
.unwrap();
sleep(Duration::from_millis(2001));
let value = *counter.lock().unwrap();
assert!(value >= 1, "Expected at least 1 execution, got {}", value);
}
#[test]
fn add_multiple_jobs() {
let local_tz = Local::from_offset(&FixedOffset::east_opt(7).unwrap());
let mut cron = Cron::new(local_tz);
cron.start();
let counter1 = Arc::new(Mutex::new(0));
let c1 = Arc::clone(&counter1);
cron.add_fn("* * * * * *", move || {
let mut value = c1.lock().unwrap();
*value += 1;
})
.unwrap();
let counter2 = Arc::new(Mutex::new(0));
let c2 = Arc::clone(&counter2);
cron.add_fn("*/2 * * * * *", move || {
let mut value = c2.lock().unwrap();
*value += 1;
})
.unwrap();
sleep(Duration::from_millis(2001));
let value1 = *counter1.lock().unwrap();
let value2 = *counter2.lock().unwrap();
assert!(value1 >= 1, "Counter1 expected at least 1, got {}", value1);
assert!(value2 >= 1, "Counter2 expected at least 1, got {}", value2);
}
#[test]
fn remove_job() {
let local_tz = Local::from_offset(&FixedOffset::east_opt(7).unwrap());
let mut cron = Cron::new(local_tz);
cron.start();
let counter = Arc::new(Mutex::new(0));
let counter1 = Arc::clone(&counter);
let job_id = cron
.add_fn("* * * * * *", move || {
*counter1.lock().unwrap() += 1;
})
.unwrap();
sleep(Duration::from_millis(1001));
assert!(*counter.lock().unwrap() >= 1, "Should have executed at least once");
cron.remove(job_id);
{
let mut count = counter.lock().unwrap();
*count = 0;
}
sleep(Duration::from_millis(1001));
let value = *counter.lock().unwrap();
assert_eq!(value, 0, "Should not execute after removal");
}
#[test]
fn test_invalid_cron_expressions() {
let mut cron = Cron::new(Utc);
let result = cron.add_fn("invalid", || {});
assert!(result.is_err());
match result.unwrap_err() {
CronError::ParseError(_) => {},
_ => panic!("Expected ParseError"),
}
let result = cron.add_fn("", || {});
assert!(result.is_err());
let result = cron.add_fn("* * *", || {});
assert!(result.is_err());
let result = cron.add_fn("@ # $ % ^ & *", || {});
assert!(result.is_err());
let result = cron.add_fn("70 * * * * * *", || {}); assert!(result.is_err());
let result = cron.add_fn("* 70 * * * * *", || {}); assert!(result.is_err());
let result = cron.add_fn("* * 25 * * * *", || {}); assert!(result.is_err());
}
#[test]
fn test_edge_case_cron_expressions() {
let mut cron = Cron::new(Utc);
assert!(cron.add_fn("59 59 23 * * *", || {}).is_ok()); assert!(cron.add_fn("0 0 * 1 1 *", || {}).is_ok());
assert!(cron.add_fn("0 0 * 29 2 *", || {}).is_ok());
assert!(cron.add_fn("*/15 0,30 9-17 * * MON-FRI", || {}).is_ok());
assert!(cron.add_fn("0 0 12 */2 * *", || {}).is_ok());
assert!(cron.add_fn("* * * * * *", || {}).is_ok()); assert!(cron.add_fn("* * * * * * *", || {}).is_ok()); }
#[test]
fn test_timezone_edge_cases() {
let timezones = vec![
FixedOffset::east_opt(0).unwrap(), FixedOffset::east_opt(12 * 3600).unwrap(), FixedOffset::west_opt(12 * 3600).unwrap(), FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap(), ];
for tz in timezones {
let mut cron = Cron::new(tz);
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
let result = cron.add_fn("* * * * * *", move || {
let mut count = counter_clone.lock().unwrap();
*count += 1;
});
assert!(result.is_ok());
}
}
#[test]
fn test_job_id_generation() {
let mut cron = Cron::new(Utc);
let mut job_ids = Vec::new();
for i in 0..100 {
let job_id = cron.add_fn("* * * * * *", move || {
println!("Job {}", i);
}).unwrap();
assert!(!job_ids.contains(&job_id));
job_ids.push(job_id);
}
for i in 1..job_ids.len() {
assert!(job_ids[i] > job_ids[i-1]);
}
}
#[test]
fn test_remove_nonexistent_job() {
let mut cron = Cron::new(Utc);
cron.start();
cron.remove(999999);
let job_id = cron.add_fn("* * * * * *", || {}).unwrap();
cron.remove(job_id);
cron.remove(job_id); cron.remove(job_id); }
#[test]
fn test_stop_without_start() {
let cron = Cron::new(Utc);
cron.stop();
cron.stop(); }
#[test]
fn test_start_multiple_times() {
let mut cron = Cron::new(Utc);
cron.start();
cron.start();
cron.start();
sleep(Duration::from_millis(100));
cron.stop();
}
#[test]
fn test_add_job_after_stop() {
let mut cron = Cron::new(Utc);
cron.start();
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
cron.add_fn("* * * * * *", move || {
let mut count = counter_clone.lock().unwrap();
*count += 1;
}).unwrap();
sleep(Duration::from_millis(1100));
cron.stop();
let initial_count = *counter.lock().unwrap();
assert!(initial_count >= 1);
let result = cron.add_fn("* * * * * *", || {});
assert!(result.is_ok());
}
#[test]
fn test_startup_performance() {
let start = Instant::now();
for _ in 0..100 {
let mut cron = Cron::new(Utc);
cron.start();
cron.stop();
}
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(5000),
"Creating 100 cron instances took too long: {:?}", elapsed);
}
#[test]
fn test_job_addition_performance() {
let mut cron = Cron::new(Utc);
let start = Instant::now();
for i in 0..1000 {
cron.add_fn("* * * * * *", move || {
let _ = i; }).unwrap();
}
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(1000),
"Adding 1000 jobs took too long: {:?}", elapsed);
}
#[test]
fn test_memory_usage_with_many_jobs() {
let mut cron = Cron::new(Utc);
cron.start();
let counter = Arc::new(Mutex::new(0));
let mut job_ids = Vec::new();
for i in 0..500 {
let counter_clone = Arc::clone(&counter);
let job_id = cron.add_fn("*/5 * * * * *", move || { let mut count = counter_clone.lock().unwrap();
*count += 1;
let _ = i * 2;
}).unwrap();
job_ids.push(job_id);
}
sleep(Duration::from_millis(1000));
let start = Instant::now();
for job_id in job_ids {
cron.remove(job_id);
}
let removal_time = start.elapsed();
cron.stop();
assert!(removal_time < Duration::from_millis(100),
"Removing 500 jobs took too long: {:?}", removal_time);
}
#[test]
fn test_real_world_scheduling_scenario() {
let mut cron = Cron::new(Utc);
cron.start();
let log = Arc::new(Mutex::new(Vec::new()));
let backup_log = Arc::clone(&log);
cron.add_fn("*/2 * * * * *", move || {
backup_log.lock().unwrap().push("Database backup completed".to_string());
}).unwrap();
let cleanup_log = Arc::clone(&log);
cron.add_fn("*/3 * * * * *", move || {
cleanup_log.lock().unwrap().push("Temporary files cleaned".to_string());
}).unwrap();
let health_log = Arc::clone(&log);
cron.add_fn("* * * * * *", move || {
health_log.lock().unwrap().push("Health check performed".to_string());
}).unwrap();
sleep(Duration::from_millis(4100));
cron.stop();
let final_log = log.lock().unwrap();
assert!(final_log.iter().any(|msg| msg.contains("Database backup")));
assert!(final_log.iter().any(|msg| msg.contains("Temporary files")));
assert!(final_log.iter().any(|msg| msg.contains("Health check")));
let health_count = final_log.iter().filter(|msg| msg.contains("Health check")).count();
let backup_count = final_log.iter().filter(|msg| msg.contains("Database backup")).count();
assert!(health_count >= 3, "Health checks should run frequently, got {}", health_count);
assert!(backup_count >= 1, "Backup should run at least once, got {}", backup_count);
assert!(health_count >= backup_count, "Health checks should be at least as frequent as backups");
}
#[test]
fn test_complex_scheduling_patterns() {
let mut cron = Cron::new(Utc);
let job1 = cron.add_fn("0 30 14 * * MON-FRI *", || {}).unwrap();
let job2 = cron.add_fn("0 0 9,17 * * * *", || {}).unwrap();
let job3 = cron.add_fn("0 */15 * * * * *", || {}).unwrap();
cron.start();
thread::sleep(Duration::from_millis(100));
cron.stop();
cron.remove(job1);
cron.remove(job2);
cron.remove(job3);
}
#[test]
fn test_timezone_sensitive_scheduling() {
let timezones = vec![
("UTC", FixedOffset::east_opt(0).unwrap()),
("Tokyo", FixedOffset::east_opt(9 * 3600).unwrap()),
("New York", FixedOffset::west_opt(5 * 3600).unwrap()),
];
for (name, tz) in timezones {
let mut cron = Cron::new(tz);
cron.start();
let execution_count = Arc::new(Mutex::new(0));
let count_clone = Arc::clone(&execution_count);
cron.add_fn("* * * * * *", move || {
let mut count = count_clone.lock().unwrap();
*count += 1;
}).unwrap();
sleep(Duration::from_millis(1500)); cron.stop();
let final_count = *execution_count.lock().unwrap();
assert!(final_count >= 1, "Timezone {} should work correctly, got {} executions", name, final_count);
}
}
#[test]
fn test_concurrent_job_access() {
let mut cron = Cron::new(Utc);
cron.start();
let shared_counter = Arc::new(AtomicUsize::new(0));
let execution_count = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&shared_counter);
let exec_clone = Arc::clone(&execution_count);
cron.add_fn("* * * * * *", move || {
let old_value = counter_clone.load(Ordering::SeqCst);
std::thread::sleep(Duration::from_millis(1)); counter_clone.store(old_value + 1, Ordering::SeqCst);
exec_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
sleep(Duration::from_millis(3100));
cron.stop();
let final_counter = shared_counter.load(Ordering::SeqCst);
let executions = execution_count.load(Ordering::SeqCst);
assert_eq!(final_counter, executions,
"Shared counter should equal execution count (no data races)");
assert!(executions >= 2, "Should have multiple executions");
}
#[test]
fn test_multiple_threads_adding_jobs() {
let cron = Arc::new(Mutex::new(Cron::new(Utc)));
let execution_counts = Arc::new(Mutex::new(Vec::new()));
{
let mut cron_guard = cron.lock().unwrap();
cron_guard.start();
}
let mut handles = vec![];
for thread_id in 0..5 {
let cron_clone = Arc::clone(&cron);
let counts_clone = Arc::clone(&execution_counts);
let handle = thread::spawn(move || {
let execution_count = Arc::new(AtomicUsize::new(0));
let count_clone = Arc::clone(&execution_count);
{
let mut cron_guard = cron_clone.lock().unwrap();
cron_guard.add_fn("* * * * * *", move || {
count_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
}
thread::sleep(Duration::from_millis(2100));
let final_count = execution_count.load(Ordering::SeqCst);
counts_clone.lock().unwrap().push((thread_id, final_count));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
{
let cron_guard = cron.lock().unwrap();
cron_guard.stop();
}
let counts = execution_counts.lock().unwrap();
assert_eq!(counts.len(), 5, "All threads should have completed");
for (thread_id, count) in counts.iter() {
assert!(count >= &1, "Thread {} should have executed at least once, got {}", thread_id, count);
}
}
#[test]
fn test_shared_mutable_state_safety() {
let mut cron = Cron::new(Utc);
cron.start();
let shared_data = Arc::new(Mutex::new(Vec::new()));
let read_count = Arc::new(AtomicUsize::new(0));
let data_clone1 = Arc::clone(&shared_data);
cron.add_fn("* * * * * *", move || {
let mut data = data_clone1.lock().unwrap();
data.push(std::thread::current().id());
}).unwrap();
let data_clone2 = Arc::clone(&shared_data);
let read_clone = Arc::clone(&read_count);
cron.add_fn("* * * * * *", move || {
let data = data_clone2.lock().unwrap();
let _len = data.len(); read_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
sleep(Duration::from_millis(3100));
cron.stop();
let final_data = shared_data.lock().unwrap();
let reads = read_count.load(Ordering::SeqCst);
assert!(final_data.len() >= 2, "Should have written data");
assert!(reads >= 2, "Should have read data");
for thread_id in final_data.iter() {
assert_ne!(format!("{:?}", thread_id), "");
}
}
#[test]
fn test_job_removal_thread_safety() {
let mut cron = Cron::new(Utc);
cron.start();
let job_ids = Arc::new(Mutex::new(Vec::new()));
let execution_count = Arc::new(AtomicUsize::new(0));
for i in 0..10 {
let count_clone = Arc::clone(&execution_count);
let job_id = cron.add_fn("* * * * * *", move || {
count_clone.fetch_add(1, Ordering::SeqCst);
std::thread::sleep(Duration::from_millis((i % 5 + 1) * 10));
}).unwrap();
job_ids.lock().unwrap().push(job_id);
}
sleep(Duration::from_millis(500));
let cron_clone = cron.clone();
let ids_clone = Arc::clone(&job_ids);
let removal_handle = thread::spawn(move || {
let ids = ids_clone.lock().unwrap();
for &job_id in ids.iter().take(5) {
cron_clone.remove(job_id);
std::thread::sleep(Duration::from_millis(50)); }
});
sleep(Duration::from_millis(1000));
removal_handle.join().unwrap();
cron.stop();
let final_count = execution_count.load(Ordering::SeqCst);
assert!(final_count >= 5, "Some jobs should have executed before removal");
}
#[test]
fn test_memory_ordering_consistency() {
use std::sync::atomic::{AtomicUsize, Ordering};
let mut cron = Cron::new(Utc);
cron.start();
let writes = Arc::new(AtomicUsize::new(0));
let reads = Arc::new(AtomicUsize::new(0));
let consistency_check = Arc::new(AtomicUsize::new(0));
let writes_clone = Arc::clone(&writes);
let check_clone1 = Arc::clone(&consistency_check);
cron.add_fn("* * * * * *", move || {
let write_count = writes_clone.fetch_add(1, Ordering::SeqCst);
check_clone1.store(write_count + 1, Ordering::SeqCst);
}).unwrap();
let reads_clone = Arc::clone(&reads);
let writes_read = Arc::clone(&writes);
let check_clone2 = Arc::clone(&consistency_check);
cron.add_fn("* * * * * *", move || {
let read_count = reads_clone.fetch_add(1, Ordering::SeqCst);
let write_count = writes_read.load(Ordering::SeqCst);
let check_value = check_clone2.load(Ordering::SeqCst);
if check_value > 0 && write_count > 0 {
assert!(check_value >= write_count,
"Memory ordering violation: check={}, writes={}, reads={}",
check_value, write_count, read_count);
}
}).unwrap();
sleep(Duration::from_millis(3100));
cron.stop();
let final_writes = writes.load(Ordering::SeqCst);
let final_reads = reads.load(Ordering::SeqCst);
assert!(final_writes >= 2, "Should have multiple writes");
assert!(final_reads >= 2, "Should have multiple reads");
}
#[test]
fn test_scheduler_clone_safety() {
let mut cron1 = Cron::new(Utc);
let mut cron2 = cron1.clone();
let counter1 = Arc::new(AtomicUsize::new(0));
let counter2 = Arc::new(AtomicUsize::new(0));
let c1_clone = Arc::clone(&counter1);
cron1.add_fn("* * * * * *", move || {
c1_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
let c2_clone = Arc::clone(&counter2);
cron2.add_fn("* * * * * *", move || {
c2_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
cron1.start();
cron2.start();
sleep(Duration::from_millis(2100));
cron1.stop();
cron2.stop();
let count1 = counter1.load(Ordering::SeqCst);
let count2 = counter2.load(Ordering::SeqCst);
assert!(count1 >= 1, "First cron should execute, got {}", count1);
assert!(count2 >= 1, "Second cron should execute, got {}", count2);
}
#[test]
fn test_crossbeam_channel_paths() {
let mut cron = Cron::new(Utc);
cron.start();
std::thread::sleep(Duration::from_millis(100));
let job_id = cron.add_fn("* * * * * * *", || {}).unwrap();
std::thread::sleep(Duration::from_millis(100));
cron.stop();
cron.remove(job_id);
}
#[test]
fn test_schedule_next_edge_cases() {
let mut cron = Cron::new(Utc);
let schedules = vec![
"0 0 0 29 2 * *", "59 59 23 31 12 * *", "0 0 0 1 1 * 2100", ];
for schedule in schedules {
if let Ok(job_id) = cron.add_fn(schedule, || {}) {
cron.remove(job_id);
}
}
}
#[test]
fn test_empty_entries_with_start_blocking() {
let cron = Cron::new(Utc);
let cron_clone = cron.clone();
let handle = std::thread::spawn(move || {
let mut cron_mut = cron_clone;
cron_mut.start_blocking();
});
std::thread::sleep(Duration::from_millis(50));
cron.stop();
let _ = handle.join();
}
#[test]
fn test_timer_expiry_path() {
let mut cron = Cron::new(Utc);
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
let job_id = cron.add_fn("* * * * * * *", move || {
executed_clone.store(true, Ordering::SeqCst);
}).unwrap();
cron.start();
std::thread::sleep(Duration::from_millis(1100));
cron.stop();
assert!(executed.load(Ordering::SeqCst));
cron.remove(job_id);
}
#[test]
fn test_wait_duration_calculation() {
let mut cron = Cron::new(Utc);
let job_id = cron.add_fn("0 0 0 1 1 * 2030", || {}).unwrap();
cron.start();
std::thread::sleep(Duration::from_millis(50));
cron.stop();
cron.remove(job_id);
}
#[test]
fn test_multiple_jobs_execution_order() {
let mut cron = Cron::new(Utc);
let execution_order = Arc::new(Mutex::new(Vec::new()));
for i in 0..3 {
let order_clone = execution_order.clone();
let _job_id = cron.add_fn("* * * * * * *", move || {
order_clone.lock().unwrap().push(i);
}).unwrap();
}
cron.start();
std::thread::sleep(Duration::from_millis(1100));
cron.stop();
let order = execution_order.lock().unwrap();
assert!(!order.is_empty(), "Jobs should have executed");
}
#[test]
fn test_job_due_check_logic() {
let mut cron = Cron::new(Utc);
let execution_count = Arc::new(AtomicUsize::new(0));
let count1 = execution_count.clone();
let job_id1 = cron.add_fn("* * * * * * *", move || {
count1.fetch_add(1, Ordering::SeqCst);
}).unwrap();
let count2 = execution_count.clone();
let job_id2 = cron.add_fn("*/2 * * * * * *", move || {
count2.fetch_add(1, Ordering::SeqCst);
}).unwrap();
cron.start();
std::thread::sleep(Duration::from_millis(2100));
cron.stop();
let final_count = execution_count.load(Ordering::SeqCst);
assert!(final_count >= 2, "Should have executed multiple jobs");
cron.remove(job_id1);
cron.remove(job_id2);
}
#[test]
fn test_stop_channel_receive() {
let mut cron = Cron::new(Utc);
cron.start();
std::thread::sleep(Duration::from_millis(50));
cron.stop();
std::thread::sleep(Duration::from_millis(50));
}
#[test]
fn test_precise_timing_edge_cases() {
let mut cron = Cron::new(Utc);
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
let job_id = cron.add_fn("* * * * * * *", move || {
executed_clone.store(true, Ordering::SeqCst);
}).unwrap();
cron.start();
std::thread::sleep(Duration::from_millis(1050)); cron.stop();
assert!(executed.load(Ordering::SeqCst));
cron.remove(job_id);
}
#[test]
fn test_cross_thread_job_execution() {
let mut cron = Cron::new(Utc);
let main_thread_id = thread::current().id();
let execution_threads = Arc::new(Mutex::new(Vec::new()));
let threads_clone = Arc::clone(&execution_threads);
cron.add_fn("* * * * * *", move || {
let current_thread = thread::current().id();
threads_clone.lock().unwrap().push(current_thread);
}).unwrap();
cron.start();
sleep(Duration::from_millis(2100));
cron.stop();
let threads = execution_threads.lock().unwrap();
assert!(threads.len() >= 1, "Should have executed jobs");
for &thread_id in threads.iter() {
assert_ne!(thread_id, main_thread_id,
"Jobs should execute in background threads, not main thread");
}
}
#[test]
fn test_large_scale_concurrent_execution() {
let mut cron = Cron::new(Utc);
cron.start();
let total_executions = Arc::new(AtomicUsize::new(0));
let active_jobs = Arc::new(AtomicUsize::new(0));
let max_concurrent = Arc::new(AtomicUsize::new(0));
for _ in 0..50 {
let total_clone = Arc::clone(&total_executions);
let active_clone = Arc::clone(&active_jobs);
let max_clone = Arc::clone(&max_concurrent);
cron.add_fn("* * * * * *", move || {
let current_active = active_clone.fetch_add(1, Ordering::SeqCst) + 1;
loop {
let current_max = max_clone.load(Ordering::SeqCst);
if current_active <= current_max ||
max_clone.compare_exchange_weak(current_max, current_active,
Ordering::SeqCst, Ordering::Relaxed).is_ok() {
break;
}
}
std::thread::sleep(Duration::from_millis(10));
active_clone.fetch_sub(1, Ordering::SeqCst);
total_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
}
sleep(Duration::from_millis(2100));
cron.stop();
let total = total_executions.load(Ordering::SeqCst);
let max_concurrent_jobs = max_concurrent.load(Ordering::SeqCst);
assert!(total >= 50, "Should have executed many jobs, got {}", total);
assert!(max_concurrent_jobs >= 10, "Should have high concurrency, got {}", max_concurrent_jobs);
let final_active = active_jobs.load(Ordering::SeqCst);
assert_eq!(final_active, 0, "All jobs should have completed, {} still active", final_active);
}
#[test]
fn test_unsafe_cell_in_job() {
use std::cell::UnsafeCell;
let mut cron = Cron::new(Utc);
cron.start();
let _unsafe_data = UnsafeCell::new(0i32);
let execution_count = Arc::new(AtomicUsize::new(0));
let count_clone = Arc::clone(&execution_count);
cron.add_fn("* * * * * *", move || {
let local_unsafe = UnsafeCell::new(42i32);
unsafe {
let ptr = local_unsafe.get();
*ptr += 1;
assert_eq!(*ptr, 43);
}
count_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
sleep(Duration::from_millis(2100));
cron.stop();
let final_count = execution_count.load(Ordering::SeqCst);
assert!(final_count >= 1, "Job should have executed at least once");
}
#[test]
fn test_non_sync_custom_type() {
use std::cell::UnsafeCell;
struct NonSyncCounter {
inner: UnsafeCell<i32>,
}
impl NonSyncCounter {
fn new(value: i32) -> Self {
Self {
inner: UnsafeCell::new(value),
}
}
fn increment(&self) -> i32 {
unsafe {
let ptr = self.inner.get();
*ptr += 1;
*ptr
}
}
fn get(&self) -> i32 {
unsafe { *self.inner.get() }
}
}
unsafe impl Send for NonSyncCounter {}
let mut cron = Cron::new(Utc);
cron.start();
let execution_results = Arc::new(Mutex::new(Vec::new()));
let results_clone = Arc::clone(&execution_results);
cron.add_fn("* * * * * *", move || {
let counter = NonSyncCounter::new(0);
let value1 = counter.increment(); let value2 = counter.increment(); let final_value = counter.get();
results_clone.lock().unwrap().push((value1, value2, final_value));
}).unwrap();
sleep(Duration::from_millis(2100));
cron.stop();
let results = execution_results.lock().unwrap();
assert!(results.len() >= 1, "Should have at least one execution");
for &(val1, val2, final_val) in results.iter() {
assert_eq!(val1, 1, "First increment should be 1");
assert_eq!(val2, 2, "Second increment should be 2");
assert_eq!(final_val, 2, "Final value should be 2");
}
}
#[test]
fn test_unsafe_cell_performance_comparison() {
use std::cell::UnsafeCell;
let mut cron = Cron::new(Utc);
cron.start();
let unsafe_timings = Arc::new(Mutex::new(Vec::new()));
let mutex_timings = Arc::new(Mutex::new(Vec::new()));
let unsafe_times = Arc::clone(&unsafe_timings);
cron.add_fn("* * * * * *", move || {
let unsafe_counter = UnsafeCell::new(0u64);
let start = Instant::now();
unsafe {
let ptr = unsafe_counter.get();
for _ in 0..1000 {
*ptr += 1;
}
}
let elapsed = start.elapsed();
unsafe_times.lock().unwrap().push(elapsed);
}).unwrap();
let mutex_times = Arc::clone(&mutex_timings);
cron.add_fn("*/2 * * * * *", move || {
let mutex_counter = Mutex::new(0u64);
let start = Instant::now();
let mut counter = mutex_counter.lock().unwrap();
for _ in 0..1000 {
*counter += 1;
}
let elapsed = start.elapsed();
mutex_times.lock().unwrap().push(elapsed);
}).unwrap();
sleep(Duration::from_millis(3100));
cron.stop();
let unsafe_times = unsafe_timings.lock().unwrap();
let mutex_times = mutex_timings.lock().unwrap();
assert!(unsafe_times.len() >= 2, "Should have UnsafeCell timings");
assert!(mutex_times.len() >= 1, "Should have Mutex timings");
let unsafe_avg = unsafe_times.iter().sum::<Duration>() / unsafe_times.len() as u32;
let mutex_avg = mutex_times.iter().sum::<Duration>() / mutex_times.len() as u32;
println!("UnsafeCell average time: {:?}", unsafe_avg);
println!("Mutex average time: {:?}", mutex_avg);
assert!(unsafe_avg < Duration::from_millis(100), "UnsafeCell should be reasonably fast");
assert!(mutex_avg < Duration::from_millis(100), "Mutex should be reasonably fast");
}
#[test]
fn test_local_unsafe_storage() {
use std::cell::UnsafeCell;
struct LocalUnsafeStorage {
data: UnsafeCell<HashMap<String, i32>>,
}
impl LocalUnsafeStorage {
fn new() -> Self {
Self {
data: UnsafeCell::new(HashMap::new()),
}
}
fn insert(&self, key: String, value: i32) {
unsafe {
(*self.data.get()).insert(key, value);
}
}
fn get(&self, key: &str) -> Option<i32> {
unsafe {
(*self.data.get()).get(key).copied()
}
}
fn len(&self) -> usize {
unsafe {
(*self.data.get()).len()
}
}
}
unsafe impl Send for LocalUnsafeStorage {}
let mut cron = Cron::new(Utc);
cron.start();
let operation_results = Arc::new(Mutex::new(Vec::new()));
let results_clone = Arc::clone(&operation_results);
cron.add_fn("* * * * * *", move || {
let storage = LocalUnsafeStorage::new();
storage.insert("key1".to_string(), 42);
storage.insert("key2".to_string(), 99);
storage.insert("key3".to_string(), 123);
let val1 = storage.get("key1");
let val2 = storage.get("key2");
let val3 = storage.get("key3");
let missing = storage.get("missing");
let final_len = storage.len();
results_clone.lock().unwrap().push((val1, val2, val3, missing, final_len));
}).unwrap();
sleep(Duration::from_millis(2100));
cron.stop();
let results = operation_results.lock().unwrap();
assert!(results.len() >= 1, "Should have performed operations");
for &(val1, val2, val3, missing, len) in results.iter() {
assert_eq!(val1, Some(42), "Should read correct value for key1");
assert_eq!(val2, Some(99), "Should read correct value for key2");
assert_eq!(val3, Some(123), "Should read correct value for key3");
assert_eq!(missing, None, "Should return None for missing key");
assert_eq!(len, 3, "Should have 3 entries");
}
}
#[test]
fn test_unsafe_cell_with_complex_data() {
use std::cell::UnsafeCell;
let mut cron = Cron::new(Utc);
cron.start();
let execution_results = Arc::new(Mutex::new(Vec::new()));
let results_clone = Arc::clone(&execution_results);
cron.add_fn("* * * * * *", move || {
let complex_data = UnsafeCell::new(vec![
HashMap::new(),
HashMap::new(),
HashMap::new(),
]);
unsafe {
let data_ptr = complex_data.get();
let data = &mut *data_ptr;
data[0].insert("section0".to_string(), 100);
data[1].insert("section1".to_string(), 200);
data[2].insert("section2".to_string(), 300);
let val0 = data[0].get("section0").copied();
let val1 = data[1].get("section1").copied();
let val2 = data[2].get("section2").copied();
let total_sections = data.len();
let total_items: usize = data.iter().map(|map| map.len()).sum();
results_clone.lock().unwrap().push((val0, val1, val2, total_sections, total_items));
}
}).unwrap();
sleep(Duration::from_millis(2100));
cron.stop();
let results = execution_results.lock().unwrap();
assert!(results.len() >= 1, "Should have at least one execution");
for &(val0, val1, val2, sections, items) in results.iter() {
assert_eq!(val0, Some(100), "Section 0 should have value 100");
assert_eq!(val1, Some(200), "Section 1 should have value 200");
assert_eq!(val2, Some(300), "Section 2 should have value 300");
assert_eq!(sections, 3, "Should have 3 sections");
assert_eq!(items, 3, "Should have 3 total items");
}
}
#[test]
fn test_remove_from_stopped_scheduler_sync() {
let mut cron = Cron::new(Utc);
let job_id = cron.add_fn("* * * * * * *", || {
println!("Test job");
}).unwrap();
cron.remove(job_id);
cron.remove(job_id);
cron.remove(9999); }
#[test]
fn test_start_blocking_edge_cases() {
let mut cron = Cron::new(Utc);
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
let _job_id = cron.add_fn("* * * * * * *", move || {
executed_clone.store(true, Ordering::SeqCst);
}).unwrap();
let mut cron_clone = cron.clone();
let handle = thread::spawn(move || {
cron_clone.start_blocking();
});
thread::sleep(Duration::from_millis(1100));
cron.stop();
let _ = handle.join();
assert!(executed.load(Ordering::SeqCst));
}
#[test]
fn test_scheduler_with_no_jobs_sync() {
let mut cron = Cron::new(Utc);
cron.start();
thread::sleep(Duration::from_millis(100));
cron.stop();
let job_id = cron.add_fn("* * * * * * *", || {}).unwrap();
cron.remove(job_id);
}
#[test]
fn test_stop_channel_edge_cases_sync() {
let mut cron = Cron::new(Utc);
cron.stop(); cron.stop(); cron.stop();
cron.start();
cron.stop();
cron.start();
let job_id = cron.add_fn("* * * * * * *", || {}).unwrap();
cron.stop();
cron.remove(job_id);
}
#[test]
fn test_job_scheduling_edge_cases_sync() {
let mut cron = Cron::new(Utc);
let far_future_job = cron.add_fn("0 0 0 1 1 * 2030", || {
println!("Far future job");
}).unwrap();
let immediate_job = cron.add_fn("* * * * * * *", || {
println!("Immediate job");
}).unwrap();
cron.start();
thread::sleep(Duration::from_millis(100));
cron.stop();
cron.remove(far_future_job);
cron.remove(immediate_job);
}
#[test]
fn test_schedule_method_coverage_sync() {
let mut cron = Cron::new(Utc);
let job_id1 = cron.add_fn("* * * * * * *", || {}).unwrap();
cron.start();
let job_id2 = cron.add_fn("*/2 * * * * * *", || {}).unwrap();
let job_id3 = cron.add_fn("*/3 * * * * * *", || {}).unwrap();
thread::sleep(Duration::from_millis(100));
cron.stop();
cron.remove(job_id1);
cron.remove(job_id2);
cron.remove(job_id3);
}
#[test]
fn test_entry_schedule_next_edge_cases() {
let mut cron = Cron::new(Utc);
let job_id = cron.add_fn("0 0 0 29 2 * *", || {}).unwrap();
cron.start();
thread::sleep(Duration::from_millis(100));
cron.stop();
cron.remove(job_id);
}
#[test]
fn test_set_timezone_functionality() {
let initial_tz = FixedOffset::east_opt(0).unwrap(); let mut cron = Cron::new(initial_tz);
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let job_id = cron.add_fn("* * * * * * *", move || {
counter_clone.fetch_add(1, Ordering::SeqCst);
}).unwrap();
let tokyo_tz = FixedOffset::east_opt(9 * 3600).unwrap();
cron.set_timezone(tokyo_tz);
cron.start();
thread::sleep(Duration::from_millis(1100));
cron.stop();
let count = counter.load(Ordering::SeqCst);
assert!(count >= 1, "Job should execute after timezone change");
cron.remove(job_id);
}
}