//! Shared SIGINT-guard conformance vectors //! (`docs/gpu-probe-sigint-framing.md` §7c). //! //! # Why these live here rather than in each suite //! //! The contract is that the **shell** consumer (`scripts/gate`) and the //! **Rust** consumer (R-d's `sigint_diagnosis`) agree on every case. //! Two independently written copies of the list can drift while both //! still report "45 cases" — the same-length-different-content //! divergence this matrix exists to rule out. One generator, two //! consumers. /// What a consumer must do with a given `(status, stdout)` pair. /// /// `ValidatedError` and `Boundary` **both exit 2**, so a test comparing /// only exit codes cannot separate them — and a validator that accepted /// *every* status 2 would pass. They are told apart by whether the /// child's stderr is surfaced: a validated verdict speaks with the /// helper's voice; a boundary failure must not. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Outcome { Safe, ValidatedIgnored, ValidatedError, Boundary, } pub const TOKEN_SAFE: &[u8] = b"pmacs-sigint-v1:safe"; pub const TOKEN_IGNORED: &[u8] = b"pmacs-sigint-v1:ignored"; pub const TOKEN_ERROR: &[u8] = b"pmacs-sigint-v1:error"; /// Emitted on stderr by **every** stub, so a consumer's output can be /// searched for it: present ⇒ the child's stderr was surfaced. pub const SENTINEL: &str = "PMACS-CONFORMANCE-SENTINEL"; pub struct Case { pub name: String, pub status: i32, pub stdout: Vec, pub expect: Outcome, } /// A `/bin/sh` stub reproducing one case, sentinel included. #[must_use] pub fn stub_script(case: &Case) -> String { let octal = case.stdout.iter().fold(String::new(), |mut acc, b| { use std::fmt::Write as _; let _ = write!(acc, "\\{b:03o}"); acc }); format!( "#!/bin/sh\nprintf '{octal}'\necho '{SENTINEL}' >&2\nexit {}\n", case.status ) } /// The shared set: ten classes × encodings × three statuses, plus /// X1/X3/X4. Only the diagonal validates. /// /// X2 — a spawn error with no status — is deliberately absent: the /// shell boundary cannot represent it, because an `exec` failure there /// becomes a status. Rust exercises it separately. #[must_use] pub fn shared_cases() -> Vec { let toks: [(&str, &[u8]); 3] = [ ("safe", TOKEN_SAFE), ("ignored", TOKEN_IGNORED), ("error", TOKEN_ERROR), ]; let mut out = Vec::new(); for (idx, (name, correct)) in toks.iter().enumerate() { let status = i32::try_from(idx).expect("0..=2"); let diagonal = match status { 0 => Outcome::Safe, 1 => Outcome::ValidatedIgnored, _ => Outcome::ValidatedError, }; let mut lf = correct.to_vec(); lf.push(b'\n'); out.push(Case { name: format!("{status}/V/{name}/bare"), status, stdout: correct.to_vec(), expect: diagonal, }); out.push(Case { name: format!("{status}/V/{name}/lf"), status, stdout: lf, expect: diagonal, }); for (other, bytes) in &toks { if other == name { continue; } let mut olf = bytes.to_vec(); olf.push(b'\n'); out.push(Case { name: format!("{status}/M/{other}/bare"), status, stdout: bytes.to_vec(), expect: Outcome::Boundary, }); out.push(Case { name: format!("{status}/M/{other}/lf"), status, stdout: olf, expect: Outcome::Boundary, }); } let mut leading = vec![b'\n']; leading.extend_from_slice(correct); let mut extra = correct.to_vec(); extra.extend_from_slice(b"\n\n"); let mut spaces = b" ".to_vec(); spaces.extend_from_slice(correct); spaces.push(b' '); let mut crlf = correct.to_vec(); crlf.extend_from_slice(b"\r\n"); let mut doubled = correct.to_vec(); doubled.extend_from_slice(correct); let mut nul = correct.to_vec(); nul.push(0); for (cls, bytes) in [ ("E/empty", Vec::new()), ("U/unknown", b"pmacs-sigint-v2:safe".to_vec()), ("L/leading-lf", leading), ("X/extra-lf", extra), ("S/spaces", spaces), ("C/crlf", crlf), ("D/doubled", doubled), ("N/nul", nul), ] { out.push(Case { name: format!("{status}/{cls}"), status, stdout: bytes, expect: Outcome::Boundary, }); } } out.push(Case { name: "X1/status-126".to_owned(), status: 126, stdout: TOKEN_SAFE.to_vec(), expect: Outcome::Boundary, }); out.push(Case { name: "X3/ignored-text-no-token".to_owned(), status: 1, stdout: Vec::new(), expect: Outcome::Boundary, }); out.push(Case { name: "X4/stderr-noise".to_owned(), status: 0, stdout: TOKEN_SAFE.to_vec(), expect: Outcome::Safe, }); out }