//! The debugger's Program Inspector, end-to-end against a real system debugger. //! `#[ignore]`d because it needs lldb/gdb installed and OS debugging permission; run //! it explicitly with: //! cargo test -p joule_coded --test debug_inspect -- --ignored use joule_coded::inspect_runtime; use joule_core::debugger::{Diagnostic, DiagnosticKind}; #[test] #[ignore = "needs a system debugger (lldb/gdb) + OS debugging permission"] fn inspector_captures_live_variables_at_a_fault() { let root = std::env::temp_dir().join(format!("jc-inspect-{}", std::process::id())); let src = root.join("src"); std::fs::create_dir_all(&src).unwrap(); std::fs::write( root.join("Cargo.toml"), "[package]\nname = \"broken\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\ [[bin]]\nname = \"broken\"\npath = \"src/main.rs\"\n[profile.dev]\ndebug = true\n", ) .unwrap(); std::fs::write( src.join("main.rs"), "fn divide(numerator: i32, divisor: i32) -> i32 {\n\ \x20 let ratio = numerator / divisor;\n\ \x20 ratio\n\ }\n\ fn main() {\n\ \x20 let divisor = 0;\n\ \x20 let numerator = 10;\n\ \x20 println!(\"{}\", divide(numerator, divisor));\n\ }\n", ) .unwrap(); // Blocking is the point: this test cannot proceed until the fixture is // built, and there is no event loop here for it to stall. #[allow(clippy::disallowed_methods)] let built = std::process::Command::new("cargo") .arg("build") .current_dir(&root) .status() .map(|status| status.success()) .unwrap_or(false); assert!(built, "cargo build of the broken fixture"); // The parsed panic points at the divide on line 2; inspect the live locals there. let diagnostic = Diagnostic { kind: DiagnosticKind::Panic, file: Some("src/main.rs".into()), line: Some(2), message: "attempt to divide by zero".into(), }; let state = inspect_runtime(&root, "./target/debug/broken", &diagnostic); std::fs::remove_dir_all(&root).ok(); let state = state.expect("inspector captured runtime state from the debugger"); let names: Vec<&str> = state.variables.iter().map(|variable| variable.name.as_str()).collect(); assert!( names.contains(&"numerator") && names.contains(&"divisor"), "captured the live locals at the fault: {names:?}" ); // The value the source can't tell you: divisor was actually 0. let divisor = state.variables.iter().find(|variable| variable.name == "divisor").unwrap(); assert_eq!(divisor.value, "0", "the real runtime value of divisor"); }