use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use std::hash::Hash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ThreadId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BreakpointId(pub Uuid);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceLocation {
pub file_path: String,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone)]
pub struct Breakpoint {
pub id: BreakpointId,
pub location: SourceLocation,
pub enabled: bool,
pub hit_count: u64,
pub created_at: Instant,
}
pub type BreakpointMap = HashMap<BreakpointId, Breakpoint>;
#[derive(Debug, Clone)]
pub struct ThreadInspector {
pub thread_id: ThreadId,
pub status: ThreadStatus,
pub current_location: Option<SourceLocation>,
pub variables: HashMap<String, VariableValue>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ThreadStatus {
Running,
Paused,
Stopped,
Terminated,
}
#[derive(Debug, Clone, PartialEq)]
pub enum VariableValue {
Undefined,
Null,
Boolean(bool),
Number(f64),
String(String),
Object(HashMap<String, VariableValue>),
Array(Vec<VariableValue>),
}
pub type ThreadInspectors = HashMap<ThreadId, ThreadInspector>;
#[derive(Debug)]
pub struct Debugger {
pub breakpoints: Arc<RwLock<BreakpointMap>>,
pub inspectors: Arc<RwLock<ThreadInspectors>>,
}
impl Debugger {
pub fn new() -> Self {
Self {
breakpoints: Arc::new(Mutex::new(HashMap::new()))
inspectors: Arc::new(Mutex::new(HashMap::new()))
}
}
pub async fn set_breakpoint(
&self,
location: &SourceLocation,
) -> Result<BreakpointId, Box<dyn std::error::Error + Send + Sync>> {
let breakpoint_id: _ = BreakpointId(Uuid::new_v4());
let breakpoint: _ = Breakpoint {
id: breakpoint_id,
location: location.clone(),
enabled: true,
hit_count: 0,
created_at: Instant::now(),
};
let mut breakpoints = self.breakpoints.write().map_err(|_| "Failed to acquire write lock")?;
breakpoints.insert(breakpoint_id, breakpoint);
Ok(breakpoint_id)
}
pub async fn remove_breakpoint(
&self,
breakpoint_id: &BreakpointId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut breakpoints = self.breakpoints.write().map_err(|_| "Failed to acquire write lock")?;
breakpoints.remove(breakpoint_id);
Ok(())
}
pub async fn toggle_breakpoint(
&self,
breakpoint_id: &BreakpointId,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let mut breakpoints = self.breakpoints.write().map_err(|_| "Failed to acquire write lock")?;
if let Some(breakpoint) = breakpoints.get_mut(breakpoint_id) {
breakpoint.enabled = !breakpoint.enabled;
Ok(breakpoint.enabled)
} else {
Err("Breakpoint not found".into())
}
}
pub async fn get_breakpoints(
&self,
) -> Result<Vec<Breakpoint>, Box<dyn std::error::Error + Send + Sync>> {
let breakpoints: _ = self.breakpoints.read().map_err(|_| "Failed to acquire read lock")?;
Ok(breakpoints.values().cloned().collect())
}
pub async fn inspect_thread(
&self,
thread_id: ThreadId,
) -> Result<ThreadState, Box<dyn std::error::Error + Send + Sync>> {
let inspectors: _ = self.inspectors.read().map_err(|_| "Failed to acquire read lock")?;
if let Some(inspector) = inspectors.get(&thread_id) {
Ok(ThreadState {
thread_id,
status: inspector.status.clone(),
current_location: inspector.current_location.clone(),
variables: inspector.variables.clone(),
})
} else {
Err("Thread not found".into())
}
}
pub async fn register_thread(
&self,
thread_id: ThreadId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut inspectors = self.inspectors.write().map_err(|_| "Failed to acquire write lock")?;
if !inspectors.contains_key(&thread_id) {
let inspector: _ = ThreadInspector {
thread_id,
status: ThreadStatus::Running,
current_location: None,
variables: HashMap::new(),
};
inspectors.insert(thread_id, inspector);
}
Ok(())
}
pub async fn pause_thread(
&self,
thread_id: ThreadId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut inspectors = self.inspectors.write().map_err(|_| "Failed to acquire write lock")?;
if let Some(inspector) = inspectors.get_mut(&thread_id) {
inspector.status = ThreadStatus::Paused;
Ok(())
} else {
Err("Thread not found".into())
}
}
pub async fn resume_thread(
&self,
thread_id: ThreadId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut inspectors = self.inspectors.write().map_err(|_| "Failed to acquire write lock")?;
if let Some(inspector) = inspectors.get_mut(&thread_id) {
inspector.status = ThreadStatus::Running;
Ok(())
} else {
Err("Thread not found".into())
}
}
pub async fn update_thread_state(
&self,
thread_id: ThreadId,
location: Option<SourceLocation>,
variables: HashMap<String, VariableValue>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut inspectors = self.inspectors.write().map_err(|_| "Failed to acquire write lock")?;
if let Some(inspector) = inspectors.get_mut(&thread_id) {
inspector.current_location = location;
inspector.variables = variables;
Ok(())
} else {
Err("Thread not found".into())
}
}
pub async fn get_all_threads(
&self,
) -> Result<Vec<ThreadState>, Box<dyn std::error::Error + Send + Sync>> {
let inspectors: _ = self.inspectors.read().map_err(|_| "Failed to acquire read lock")?;
Ok(inspectors
.values()
.map(|inspector| ThreadState {
thread_id: inspector.thread_id,
status: inspector.status.clone(),
current_location: inspector.current_location.clone(),
variables: inspector.variables.clone(),
})
.collect())
}
}
#[derive(Debug, Clone)]
pub struct ThreadState {
pub thread_id: ThreadId,
pub status: ThreadStatus,
pub current_location: Option<SourceLocation>,
pub variables: HashMap<String, VariableValue>,
}
impl Default for Debugger {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_breakpoint_management() {
let debugger: _ = Debugger::new();
let location: _ = SourceLocation {
file_path: "test.js".to_string(),
line: 10,
column: 5,
};
let breakpoint_id: _ = debugger.set_breakpoint(&location).await.unwrap();
let breakpoints: _ = debugger.get_breakpoints().await.unwrap();
assert_eq!(breakpoints.len(), 1);
assert_eq!(breakpoints[0].id, breakpoint_id);
assert_eq!(breakpoints[0].location, location);
assert!(breakpoints[0].enabled);
let enabled: _ = debugger.toggle_breakpoint(&breakpoint_id).await.unwrap();
assert!(!enabled);
debugger.remove_breakpoint(&breakpoint_id).await.unwrap();
let breakpoints: _ = debugger.get_breakpoints().await.unwrap();
assert_eq!(breakpoints.len(), 0);
}
#[tokio::test]
async fn test_multithread_debugging() {
let debugger: _ = Debugger::new();
let thread_id: _ = ThreadId(1);
debugger.register_thread(thread_id).await.unwrap();
let thread_state: _ = debugger.inspect_thread(thread_id).await.unwrap();
assert_eq!(thread_state.thread_id, thread_id);
assert_eq!(thread_state.status, ThreadStatus::Running);
debugger.pause_thread(thread_id).await.unwrap();
let thread_state: _ = debugger.inspect_thread(thread_id).await.unwrap();
assert_eq!(thread_state.status, ThreadStatus::Paused);
debugger.resume_thread(thread_id).await.unwrap();
let thread_state: _ = debugger.inspect_thread(thread_id).await.unwrap();
assert_eq!(thread_state.status, ThreadStatus::Running);
let location: _ = SourceLocation {
file_path: "test.js".to_string(),
line: 20,
column: 10,
};
let mut variables = HashMap::new();
variables.insert(
"x".to_string(),
VariableValue::Number(42.0),
);
debugger
.update_thread_state(thread_id, Some(location.clone()), variables)
.await
.unwrap();
let thread_state: _ = debugger.inspect_thread(thread_id).await.unwrap();
assert_eq!(thread_state.current_location, Some(location));
assert_eq!(
thread_state.variables.get("x"),
Some(&VariableValue::Number(42.0));
}
#[tokio::test]
async fn test_get_all_threads() {
let debugger: _ = Debugger::new();
debugger.register_thread(ThreadId(1)).await.unwrap();
debugger.register_thread(ThreadId(2)).await.unwrap();
debugger.register_thread(ThreadId(3)).await.unwrap();
let threads: _ = debugger.get_all_threads().await.unwrap();
assert_eq!(threads.len(), 3);
let thread_ids: Vec<ThreadId> = threads.iter().map(|t| t.thread_id).collect();
assert!(thread_ids.contains(&ThreadId(1));
assert!(thread_ids.contains(&ThreadId(2));
assert!(thread_ids.contains(&ThreadId(3));
}
}