1use crate::types::{Breakpoint, DebugSession, DebugState, SourceBreakpoint};
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex};
4use tracing::info;
5use uuid::Uuid;
6
7pub struct DebugManager {
8 sessions: Arc<Mutex<HashMap<String, DebugSession>>>,
9 breakpoints: Arc<Mutex<HashMap<String, Vec<Breakpoint>>>>,
10}
11
12impl Default for DebugManager {
13 fn default() -> Self {
14 Self::new()
15 }
16}
17
18impl DebugManager {
19 pub fn new() -> Self {
20 DebugManager {
21 sessions: Arc::new(Mutex::new(HashMap::new())),
22 breakpoints: Arc::new(Mutex::new(HashMap::new())),
23 }
24 }
25
26 pub fn create_session(&self, name: String, adapter_type: String) -> Result<String, String> {
27 let session_id = Uuid::new_v4().to_string();
28 let session =
29 DebugSession { id: session_id.clone(), name, state: DebugState::Stopped, adapter_type };
30
31 let mut sessions = self.sessions.lock().map_err(|e| e.to_string())?;
32 sessions.insert(session_id.clone(), session);
33
34 info!(id = %session_id, "Created debug session");
35 Ok(session_id)
36 }
37
38 pub fn get_session(&self, session_id: &str) -> Result<DebugSession, String> {
39 let sessions = self.sessions.lock().map_err(|e| e.to_string())?;
40 sessions.get(session_id).cloned().ok_or_else(|| format!("Session {} not found", session_id))
41 }
42
43 pub fn list_sessions(&self) -> Result<Vec<DebugSession>, String> {
44 let sessions = self.sessions.lock().map_err(|e| e.to_string())?;
45 Ok(sessions.values().cloned().collect())
46 }
47
48 pub fn update_session_state(&self, session_id: &str, state: DebugState) -> Result<(), String> {
49 let mut sessions = self.sessions.lock().map_err(|e| e.to_string())?;
50 if let Some(session) = sessions.get_mut(session_id) {
51 session.state = state;
52 info!(id = %session_id, "Updated session state");
53 Ok(())
54 } else {
55 Err(format!("Session {} not found", session_id))
56 }
57 }
58
59 pub fn terminate_session(&self, session_id: &str) -> Result<(), String> {
60 let mut sessions = self.sessions.lock().map_err(|e| e.to_string())?;
61 sessions.remove(session_id);
62 info!(id = %session_id, "Terminated debug session");
63 Ok(())
64 }
65
66 pub fn set_breakpoints(
67 &self, file_path: String, breakpoints: Vec<SourceBreakpoint>,
68 ) -> Result<(), String> {
69 let converted_bps: Vec<Breakpoint> = breakpoints
71 .into_iter()
72 .map(|sbp| Breakpoint {
73 id: None,
74 verified: false,
75 message: None,
76 source: None,
77 line: Some(sbp.line),
78 column: sbp.column,
79 })
80 .collect();
81
82 let mut bps = self.breakpoints.lock().map_err(|e| e.to_string())?;
83 bps.insert(file_path.clone(), converted_bps);
84 info!(path = %file_path, "Set breakpoints");
85 Ok(())
86 }
87
88 pub fn get_breakpoints(&self, file_path: &str) -> Result<Vec<Breakpoint>, String> {
89 let bps = self.breakpoints.lock().map_err(|e| e.to_string())?;
90 Ok(bps.get(file_path).cloned().unwrap_or_default())
91 }
92
93 pub fn clear_breakpoints(&self, file_path: &str) -> Result<(), String> {
94 let mut bps = self.breakpoints.lock().map_err(|e| e.to_string())?;
95 bps.remove(file_path);
96 info!(path = %file_path, "Cleared breakpoints");
97 Ok(())
98 }
99
100 pub fn get_all_breakpoints(&self) -> Result<HashMap<String, Vec<Breakpoint>>, String> {
101 let bps = self.breakpoints.lock().map_err(|e| e.to_string())?;
102 Ok(bps.clone())
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::types::DebugState;
110
111 #[test]
112 fn test_debug_manager_new() {
113 let manager = DebugManager::new();
114 let sessions = manager.list_sessions().unwrap();
116 assert!(sessions.is_empty(), "New manager should have no sessions");
117 }
118
119 #[test]
120 fn test_debug_manager_create_session() {
121 let manager = DebugManager::new();
122 let session_id = manager
123 .create_session("Test Session".to_string(), "cppdbg".to_string())
124 .unwrap();
125 assert!(!session_id.is_empty(), "Session ID should not be empty");
126 }
127
128 #[test]
129 fn test_debug_manager_get_session() {
130 let manager = DebugManager::new();
131 let session_id = manager
132 .create_session("My Debug".to_string(), "python".to_string())
133 .unwrap();
134
135 let session = manager.get_session(&session_id).unwrap();
136 assert_eq!(session.id, session_id);
137 assert_eq!(session.name, "My Debug");
138 assert_eq!(session.adapter_type, "python");
139 assert_eq!(session.state, DebugState::Stopped);
140 }
141
142 #[test]
143 fn test_debug_manager_get_nonexistent_session() {
144 let manager = DebugManager::new();
145 let result = manager.get_session("nonexistent-id");
146 assert!(result.is_err());
147 assert!(result.unwrap_err().contains("not found"));
148 }
149
150 #[test]
151 fn test_debug_manager_list_sessions() {
152 let manager = DebugManager::new();
153
154 assert!(manager.list_sessions().unwrap().is_empty());
156
157 let id1 = manager.create_session("S1".to_string(), "cppdbg".to_string()).unwrap();
159 let id2 = manager.create_session("S2".to_string(), "python".to_string()).unwrap();
160
161 let sessions = manager.list_sessions().unwrap();
162 assert_eq!(sessions.len(), 2);
163
164 let ids: Vec<&str> = sessions.iter().map(|s| s.id.as_str()).collect();
165 assert!(ids.contains(&id1.as_str()));
166 assert!(ids.contains(&id2.as_str()));
167 }
168
169 #[test]
170 fn test_debug_manager_update_session_state() {
171 let manager = DebugManager::new();
172 let session_id = manager
173 .create_session("State Test".to_string(), "go".to_string())
174 .unwrap();
175
176 let session = manager.get_session(&session_id).unwrap();
178 assert_eq!(session.state, DebugState::Stopped);
179
180 manager
182 .update_session_state(&session_id, DebugState::Running)
183 .unwrap();
184 let session = manager.get_session(&session_id).unwrap();
185 assert_eq!(session.state, DebugState::Running);
186
187 manager
189 .update_session_state(&session_id, DebugState::Paused)
190 .unwrap();
191 let session = manager.get_session(&session_id).unwrap();
192 assert_eq!(session.state, DebugState::Paused);
193
194 manager
196 .update_session_state(&session_id, DebugState::Terminated)
197 .unwrap();
198 let session = manager.get_session(&session_id).unwrap();
199 assert_eq!(session.state, DebugState::Terminated);
200 }
201
202 #[test]
203 fn test_debug_manager_update_nonexistent_session_state() {
204 let manager = DebugManager::new();
205 let result = manager.update_session_state("no-such-id", DebugState::Running);
206 assert!(result.is_err());
207 assert!(result.unwrap_err().contains("not found"));
208 }
209
210 #[test]
211 fn test_debug_manager_terminate_session() {
212 let manager = DebugManager::new();
213 let session_id = manager
214 .create_session("To Terminate".to_string(), "rust".to_string())
215 .unwrap();
216
217 assert!(manager.get_session(&session_id).is_ok());
219
220 manager.terminate_session(&session_id).unwrap();
222
223 assert!(manager.get_session(&session_id).is_err());
225 }
226
227 #[test]
228 fn test_debug_manager_terminate_nonexistent_session() {
229 let manager = DebugManager::new();
230 let result = manager.terminate_session("nonexistent");
232 assert!(result.is_ok());
233 }
234
235 #[test]
236 fn test_debug_manager_set_and_get_breakpoints() {
237 let manager = DebugManager::new();
238
239 let breakpoints = vec![SourceBreakpoint {
240 line: 10,
241 column: None,
242 condition: None,
243 hit_condition: None,
244 log_message: None,
245 }];
246
247 manager
248 .set_breakpoints("/src/main.rs".to_string(), breakpoints)
249 .unwrap();
250
251 let result = manager.get_breakpoints("/src/main.rs").unwrap();
252 assert_eq!(result.len(), 1);
253 assert_eq!(result[0].line, Some(10));
254 assert!(!result[0].verified);
255 }
256
257 #[test]
258 fn test_debug_manager_get_breakpoints_missing_file() {
259 let manager = DebugManager::new();
260 let result = manager.get_breakpoints("/nonexistent/file.rs").unwrap();
261 assert!(result.is_empty(), "Missing file should return empty breakpoints");
262 }
263
264 #[test]
265 fn test_debug_manager_clear_breakpoints() {
266 let manager = DebugManager::new();
267
268 let breakpoints = vec![SourceBreakpoint {
269 line: 5,
270 column: Some(2),
271 condition: Some("x > 0".to_string()),
272 hit_condition: None,
273 log_message: None,
274 }];
275
276 manager
277 .set_breakpoints("/app.py".to_string(), breakpoints)
278 .unwrap();
279 assert_eq!(manager.get_breakpoints("/app.py").unwrap().len(), 1);
280
281 manager.clear_breakpoints("/app.py").unwrap();
282 assert!(manager.get_breakpoints("/app.py").unwrap().is_empty());
283 }
284
285 #[test]
286 fn test_debug_manager_get_all_breakpoints() {
287 let manager = DebugManager::new();
288
289 assert!(manager.get_all_breakpoints().unwrap().is_empty());
290
291 manager
292 .set_breakpoints(
293 "/a.rs".to_string(),
294 vec![SourceBreakpoint {
295 line: 1,
296 column: None,
297 condition: None,
298 hit_condition: None,
299 log_message: None,
300 }],
301 )
302 .unwrap();
303
304 manager
305 .set_breakpoints(
306 "/b.rs".to_string(),
307 vec![SourceBreakpoint {
308 line: 2,
309 column: None,
310 condition: None,
311 hit_condition: None,
312 log_message: None,
313 }],
314 )
315 .unwrap();
316
317 let all = manager.get_all_breakpoints().unwrap();
318 assert_eq!(all.len(), 2);
319 assert!(all.contains_key("/a.rs"));
320 assert!(all.contains_key("/b.rs"));
321 }
322
323 #[test]
324 fn test_debug_manager_set_breakpoints_overwrites() {
325 let manager = DebugManager::new();
326
327 let bp1 = vec![SourceBreakpoint {
328 line: 1,
329 column: None,
330 condition: None,
331 hit_condition: None,
332 log_message: None,
333 }];
334 let bp2 = vec![
335 SourceBreakpoint {
336 line: 10,
337 column: None,
338 condition: None,
339 hit_condition: None,
340 log_message: None,
341 },
342 SourceBreakpoint {
343 line: 20,
344 column: Some(4),
345 condition: None,
346 hit_condition: None,
347 log_message: None,
348 },
349 ];
350
351 manager.set_breakpoints("/file.rs".to_string(), bp1).unwrap();
352 manager.set_breakpoints("/file.rs".to_string(), bp2).unwrap();
353
354 let result = manager.get_breakpoints("/file.rs").unwrap();
355 assert_eq!(result.len(), 2);
356 }
357
358 #[test]
359 fn test_debug_manager_multiple_sessions_independent() {
360 let manager = DebugManager::new();
361
362 let id1 = manager.create_session("S1".to_string(), "cppdbg".to_string()).unwrap();
363 let id2 = manager.create_session("S2".to_string(), "python".to_string()).unwrap();
364
365 manager.update_session_state(&id1, DebugState::Running).unwrap();
366 manager.update_session_state(&id2, DebugState::Paused).unwrap();
367
368 let s1 = manager.get_session(&id1).unwrap();
369 let s2 = manager.get_session(&id2).unwrap();
370 assert_eq!(s1.state, DebugState::Running);
371 assert_eq!(s2.state, DebugState::Paused);
372
373 manager.terminate_session(&id1).unwrap();
375 assert!(manager.get_session(&id1).is_err());
376 assert!(manager.get_session(&id2).is_ok());
377 }
378}