1use crate::adapter::DebugAdapter;
10use crate::types::{DebugSession, DebugState};
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use tracing::info;
15
16type AdapterConfigMap = Arc<RwLock<HashMap<String, (String, Vec<String>)>>>;
17
18pub(crate) struct DebugAdapterInfo {
20 #[allow(dead_code)]
21 pub(crate) session_id: String,
22 pub(crate) adapter: Arc<DebugAdapter>,
23 pub(crate) state: DebugState,
24}
25
26pub struct DebugAdapterPool {
28 adapters: Arc<RwLock<HashMap<String, DebugAdapterInfo>>>,
30
31 configurations: AdapterConfigMap,
33}
34
35impl DebugAdapterPool {
36 pub fn new() -> Self {
37 Self {
38 adapters: Arc::new(RwLock::new(HashMap::new())),
39 configurations: Arc::new(RwLock::new(HashMap::new())),
40 }
41 }
42
43 pub async fn register_adapter(
45 &self, adapter_type: String, adapter_command: String, adapter_args: Vec<String>,
46 ) {
47 let mut configs = self.configurations.write().await;
48 configs.insert(adapter_type.clone(), (adapter_command, adapter_args));
49 info!(adapter_type = %adapter_type, "Registered adapter configuration");
50 }
51
52 pub async fn create_session(&self, session: DebugSession) -> Result<Arc<DebugAdapter>, String> {
54 let (command, args) = {
56 let configs = self.configurations.read().await;
57 configs
58 .get(&session.adapter_type)
59 .ok_or(format!("No configuration found for adapter type {}", session.adapter_type))?
60 .clone()
61 };
62
63 let adapter = Arc::new(DebugAdapter::new(session.clone(), command, args));
65
66 adapter.start().await?;
68
69 let adapter_info = DebugAdapterInfo {
71 session_id: session.id.clone(),
72 adapter: Arc::clone(&adapter),
73 state: DebugState::Stopped,
74 };
75
76 let mut adapters = self.adapters.write().await;
77 adapters.insert(session.id.clone(), adapter_info);
78
79 info!(id = %session.id, "Started adapter for session");
80 Ok(adapter)
81 }
82
83 pub async fn get_adapter(&self, session_id: &str) -> Option<Arc<DebugAdapter>> {
85 let adapters = self.adapters.read().await;
86 adapters.get(session_id).map(|info| Arc::clone(&info.adapter))
87 }
88
89 pub async fn update_state(&self, session_id: &str, state: DebugState) -> Result<(), String> {
91 let mut adapters = self.adapters.write().await;
92 if let Some(info) = adapters.get_mut(session_id) {
93 info.state = state;
94 Ok(())
95 } else {
96 Err(format!("Session {} not found", session_id))
97 }
98 }
99
100 pub async fn stop_session(&self, session_id: &str) -> Result<(), String> {
102 let mut adapters = self.adapters.write().await;
103
104 if let Some(adapter_info) = adapters.remove(session_id) {
105 adapter_info.adapter.stop().await?;
106 info!(id = %session_id, "Stopped debug session");
107 Ok(())
108 } else {
109 Err(format!("Session {} not found", session_id))
110 }
111 }
112
113 pub async fn stop_all(&self) -> Result<(), String> {
114 let mut adapters = self.adapters.write().await;
115
116 for (session_id, adapter_info) in adapters.iter() {
117 adapter_info.adapter.stop().await?;
118 info!(id = %session_id, "Stopped debug session");
119 }
120
121 adapters.clear();
122 Ok(())
123 }
124
125 pub async fn list_sessions(&self) -> Vec<String> {
127 let adapters = self.adapters.read().await;
128 adapters.keys().cloned().collect()
129 }
130
131 pub async fn get_stats(&self) -> DebugPoolStats {
133 let adapters = self.adapters.read().await;
134 let total_sessions = adapters.len();
135
136 let mut states: HashMap<DebugState, usize> = HashMap::new();
137 for info in adapters.values() {
138 *states.entry(info.state.clone()).or_insert(0) += 1;
139 }
140
141 DebugPoolStats { total_sessions, states }
142 }
143}
144
145impl Default for DebugAdapterPool {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151#[derive(Debug, Clone)]
152pub struct DebugPoolStats {
153 pub total_sessions: usize,
154 pub states: HashMap<DebugState, usize>,
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[tokio::test]
162 async fn test_pool_new() {
163 let pool = DebugAdapterPool::new();
164 let sessions = pool.list_sessions().await;
165 assert!(sessions.is_empty(), "New pool should have no sessions");
166 }
167
168 #[tokio::test]
169 async fn test_pool_stats_empty() {
170 let pool = DebugAdapterPool::new();
171 let stats = pool.get_stats().await;
172 assert_eq!(stats.total_sessions, 0, "Empty pool should have 0 sessions");
173 assert!(stats.states.is_empty(), "Empty pool should have no state counts");
174 }
175
176 #[tokio::test]
177 async fn test_pool_register_adapter() {
178 let pool = DebugAdapterPool::new();
179 pool.register_adapter("cppdbg".to_string(), "/usr/bin/gdb".to_string(), vec![])
180 .await;
181
182 let sessions = pool.list_sessions().await;
184 assert!(sessions.is_empty(), "Registering config should not create a session");
185
186 let stats = pool.get_stats().await;
187 assert_eq!(stats.total_sessions, 0);
188 }
189
190 #[tokio::test]
191 async fn test_pool_get_adapter_nonexistent() {
192 let pool = DebugAdapterPool::new();
193 let result = pool.get_adapter("nonexistent-session").await;
194 assert!(result.is_none(), "Getting nonexistent adapter should return None");
195 }
196
197 #[tokio::test]
198 async fn test_pool_create_session_unconfigured() {
199 let pool = DebugAdapterPool::new();
200 let session = DebugSession {
201 id: "test-session".to_string(),
202 name: "Test".to_string(),
203 state: DebugState::Stopped,
204 adapter_type: "nonexistent".to_string(),
205 };
206 let result = pool.create_session(session).await;
207 assert!(result.is_err(), "Should fail when no adapter config registered");
208 assert!(result.unwrap_err().contains("No configuration found"));
209 }
210
211 #[tokio::test]
212 async fn test_pool_stop_session_nonexistent() {
213 let pool = DebugAdapterPool::new();
214 let result = pool.stop_session("nonexistent").await;
215 assert!(result.is_err());
216 assert!(result.unwrap_err().contains("not found"));
217 }
218
219 #[tokio::test]
220 async fn test_pool_update_state_nonexistent() {
221 let pool = DebugAdapterPool::new();
222 let result = pool.update_state("nonexistent", DebugState::Running).await;
223 assert!(result.is_err());
224 assert!(result.unwrap_err().contains("not found"));
225 }
226
227 #[tokio::test]
228 async fn test_pool_register_multiple_configs() {
229 let pool = DebugAdapterPool::new();
230 pool.register_adapter("cppdbg".to_string(), "/usr/bin/gdb".to_string(), vec![])
231 .await;
232 pool.register_adapter("python".to_string(), "debugpy".to_string(), vec!["--listen".to_string()])
233 .await;
234 pool.register_adapter("go".to_string(), "dlv".to_string(), vec![])
235 .await;
236
237 assert!(pool.list_sessions().await.is_empty());
239 }
240
241 #[tokio::test]
242 async fn test_pool_list_sessions_empty() {
243 let pool = DebugAdapterPool::new();
244 let sessions = pool.list_sessions().await;
245 assert!(sessions.is_empty());
246 }
247
248 #[tokio::test]
249 async fn test_pool_default_trait() {
250 let pool = DebugAdapterPool::default();
251 assert!(pool.list_sessions().await.is_empty());
252 }
253}