use crate::config::OrchestratorConfig;
use crate::parallel::ParallelExecutor;
use crate::tui::queue::DynamicQueue;
use std::sync::{atomic::AtomicUsize, Arc};
use tempfile::TempDir;
fn create_test_config() -> OrchestratorConfig {
OrchestratorConfig {
apply_command: Some("echo apply {change_id}".to_string()),
archive_command: Some("echo archive {change_id}".to_string()),
analyze_command: Some("echo analyze".to_string()),
acceptance_command: Some("echo acceptance".to_string()),
resolve_command: Some("echo resolve".to_string()),
..Default::default()
}
}
#[tokio::test]
async fn test_manual_resolve_counter_reduces_available_slots() {
let temp_dir = TempDir::new().unwrap();
let repo_root = temp_dir.path().to_path_buf();
let config = create_test_config();
let manual_resolve_counter = Arc::new(AtomicUsize::new(0));
let mut executor = ParallelExecutor::new(repo_root.clone(), config.clone(), None);
executor.set_manual_resolve_counter(manual_resolve_counter.clone());
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
0,
"Manual resolve counter should start at 0"
);
manual_resolve_counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
1,
"Manual resolve counter should be 1 after increment"
);
manual_resolve_counter.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
0,
"Manual resolve counter should return to 0 after completion"
);
}
#[tokio::test]
async fn test_multiple_manual_resolves_consume_multiple_slots() {
let temp_dir = TempDir::new().unwrap();
let repo_root = temp_dir.path().to_path_buf();
let config = create_test_config();
let manual_resolve_counter = Arc::new(AtomicUsize::new(0));
let mut executor = ParallelExecutor::new(repo_root.clone(), config.clone(), None);
executor.set_manual_resolve_counter(manual_resolve_counter.clone());
manual_resolve_counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
manual_resolve_counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
2,
"Manual resolve counter should be 2 for concurrent resolves"
);
manual_resolve_counter.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
1,
"Manual resolve counter should be 1 after one completes"
);
manual_resolve_counter.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
assert_eq!(
manual_resolve_counter.load(std::sync::atomic::Ordering::SeqCst),
0,
"Manual resolve counter should be 0 after all complete"
);
}
#[tokio::test]
async fn test_manual_resolve_completion_notifies_scheduler() {
let queue = DynamicQueue::new();
let notified = queue.notified();
queue.notify_scheduler();
tokio::time::timeout(std::time::Duration::from_secs(1), notified)
.await
.expect("scheduler notification should wake waiters");
}
#[test]
fn test_manual_resolve_counter_is_thread_safe() {
let counter = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let counter_clone = counter.clone();
std::thread::spawn(move || {
for _ in 0..100 {
counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
counter_clone.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(
counter.load(std::sync::atomic::Ordering::SeqCst),
0,
"Counter should be 0 after concurrent increment/decrement operations"
);
}