use std::process::{Child, Command, Stdio};
use std::time::Duration;
use dbgscope::dbgeng::{BreakRequest, DebugEngine, WaitOutcome};
fn a_target() -> Child {
Command::new("cmd.exe")
.args(["/c", "ping", "-n", "30", "127.0.0.1"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("could not start a target to attach to")
}
fn main() {
println!("=== GetInterrupt provenance: does a consumed request read differently? ===\n");
{
let mut theirs = a_target();
let e = DebugEngine::new();
e.attach_process(theirs.id()).expect("attach failed");
e.execute_command("g").expect("could not set it running");
let handle = e.interrupt_handle();
let asked = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(300));
handle.interrupt()
});
let waited = e.wait_for_event(15_000);
let filed = asked.join().expect("the asking thread panicked");
let filed = filed.expect("the interrupt was not delivered, so this arm tested nothing");
assert!(
matches!(filed, BreakRequest::Raised { .. }),
"the interrupt was filed as {filed:?}, so no operation was there to be ended by it"
);
assert_eq!(
waited.as_ref().ok(),
Some(&WaitOutcome::OnRequest),
"the wait ended as {waited:?} rather than on the break, so nothing here is under test"
);
let polls: Vec<bool> = (0..5).map(|_| e.interrupted().unwrap()).collect();
println!("A. a SetInterrupt that ended a running WaitForEvent");
println!(" wait -> {waited:?}, request {filed:?}");
println!(" five GetInterrupt polls afterwards: {polls:?}");
println!(
" -> {}\n",
if polls == [false; 5] {
"consumed by the wait, as the hypothesis says"
} else {
"still pending after the wait it ended -- the hypothesis is WRONG"
}
);
let _ = e.end_session();
let _ = theirs.kill();
let _ = theirs.wait();
}
{
let mut theirs = a_target();
let e = DebugEngine::new();
e.attach_process(theirs.id()).expect("attach failed");
let filed = e.interrupt_handle().interrupt().expect("interrupt failed");
assert_eq!(
filed,
BreakRequest::NothingRunning,
"the engine had an operation running, so this is not the control it claims to be"
);
let polls: Vec<bool> = (0..5).map(|_| e.interrupted().unwrap()).collect();
println!("B. a SetInterrupt with nothing waiting (control)");
println!(" five GetInterrupt polls: {polls:?}");
println!(
" -> {}\n",
if polls.first() == Some(&true) {
"readable afterwards, so arm A's answer is the engine's and not a broken probe"
} else {
"NOT readable -- the probe says nothing about arm A"
}
);
let _ = e.end_session();
let _ = theirs.kill();
let _ = theirs.wait();
}
{
let mut theirs = a_target();
let e = DebugEngine::new();
e.attach_process(theirs.id()).expect("attach failed");
e.execute_command("g").expect("could not set it running");
let handle = e.interrupt_handle();
let asked = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(300));
let first = handle.interrupt();
let second = handle.interrupt();
(first, second)
});
let waited = e.wait_for_event(15_000);
let (first, second) = asked.join().expect("the asking thread panicked");
let first = first.expect("the first interrupt was not delivered");
let second = second.expect("the second interrupt was not delivered");
assert_eq!(
waited.as_ref().ok(),
Some(&WaitOutcome::OnRequest),
"the wait ended as {waited:?} rather than on a break, so nothing here is under test"
);
let polls: Vec<bool> = (0..5).map(|_| e.interrupted().unwrap()).collect();
let one_operation = matches!(
(first, second),
(
BreakRequest::Raised { operation: a },
BreakRequest::Raised { operation: b }
) if a == b
);
println!("C. two SetInterrupts back to back, around one wait");
println!(" wait -> {waited:?}");
println!(
" requests {first:?} / {second:?}{}",
if one_operation {
" -- one operation, so its guard outlived both (necessary, not sufficient)"
} else {
" -- NOT one operation"
}
);
println!(" five GetInterrupt polls afterwards: {polls:?}");
println!(
" -> {}\n",
match (one_operation, polls == [false; 5]) {
(true, true) =>
"nothing survived -- consistent with the flag semantics \
test_get_interrupt_drain_semantics pins deterministically",
(true, false) =>
"a second request survived the wait, which a flag cannot do -- \
worth chasing against that test",
(false, _) =>
"INCONCLUSIVE: the operation was over before the second request went, so \
this ran arm D and says nothing about two at once",
}
);
let _ = e.end_session();
let _ = theirs.kill();
let _ = theirs.wait();
}
{
let mut theirs = a_target();
let e = DebugEngine::new();
e.attach_process(theirs.id()).expect("attach failed");
e.execute_command("g").expect("could not set it running");
let handle = e.interrupt_handle();
let asked = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(300));
let first = handle.interrupt();
std::thread::sleep(Duration::from_millis(400));
let second = handle.interrupt();
(first, second)
});
let waited = e.wait_for_event(15_000);
let (first, second) = asked.join().expect("the asking thread panicked");
first.expect("the first interrupt was not delivered");
let late =
second.expect("the late interrupt was not delivered, so this arm tested nothing");
assert_eq!(
waited.as_ref().ok(),
Some(&WaitOutcome::OnRequest),
"the wait ended as {waited:?} rather than on the first break, so the second was not late"
);
assert_eq!(
late,
BreakRequest::NothingRunning,
"the second request was filed as {late:?}, so the operation had not ended and this is \
arm C rather than arm D"
);
let polls: Vec<bool> = (0..5).map(|_| e.interrupted().unwrap()).collect();
println!("D. a request lodged 400ms after the wait it was too late for");
println!(" wait -> {waited:?}, the late request {late:?}");
println!(" five GetInterrupt polls: {polls:?}");
println!(
" -> {}\n",
if polls.first() == Some(&true) {
"readable, and it is the next operation that would be stopped by it"
} else {
"not readable, so a late request leaves no trace to find it by"
}
);
let _ = e.end_session();
let _ = theirs.kill();
let _ = theirs.wait();
}
println!("done");
}