diff --git a/docs/active-work.md b/docs/active-work.md index 56faf09..ff4075e 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -290,8 +290,9 @@ from #171 and #215. the shell needs them. - **Conformance vectors live in `tests/common/sigint_conformance.rs`** and are consumed by BOTH validators, so the two copies cannot drift - while each still reports "45 cases". Every stub emits a **sentinel on - stderr**, which is what separates `ValidatedError` from `Boundary` — + while each still reports "45 cases". The **branch-discriminating** cases emit a + sentinel on stderr, which is what separates `ValidatedError` from + `Boundary` — they share exit 2, so comparing codes alone let a validator that accepted every status 2 pass the whole matrix. An `Outcome` enum (Safe / ValidatedIgnored / ValidatedError / Boundary) is asserted @@ -319,9 +320,15 @@ from #171 and #215. `gate_validates_the_whole_shared_conformance_set`; token-to-stderr fails `sigint_helper_reports_safe_…`. An earlier entry said the status-2 mutation was caught only by the dedicated row — that was - true of the pre-sentinel matrix and is **superseded**: every stub now - emits a sentinel on stderr, so the matrix can see which branch - produced the exit 2. + true of the pre-sentinel matrix and is **superseded**: the + branch-discriminating cases now carry a sentinel, so the matrix can + see which branch produced the exit 2. +- **X3 and X4 deliberately carry their OWN stderr payloads**, not the + sentinel — X3 the canonical ignored wording with no token, X4 noise — + which is what makes them distinct inputs. `shared_cases()` asserts + uniqueness over `(status, stdout, stderr)`, so the earlier + 45-entries-over-43-inputs collapse cannot recur silently; reverting + either payload now fails by name. - **Two PRE-EXISTING crdt-only failures found while gating properly:** `m4_24_bare_string_glob_stays_relative` and `m4_24_d3_fallback_base_is_the_smallest_attachment_dir`. They diff --git a/tests/common/sigint_conformance.rs b/tests/common/sigint_conformance.rs index 04644b5..f7f2d68 100644 --- a/tests/common/sigint_conformance.rs +++ b/tests/common/sigint_conformance.rs @@ -29,8 +29,16 @@ 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. +/// Emitted on stderr by the **branch-discriminating** cases — the +/// cross-product rows and X1 — so a consumer's output can be searched +/// for it: present ⇒ the child's stderr was surfaced. +/// +/// **X3 and X4 deliberately carry their own payloads instead**, which +/// is what makes them distinct inputs rather than duplicates of +/// `1/E/empty` and `0/V/safe/bare`. An earlier revision gave every case +/// this same sentinel and so shipped 45 entries over 43 distinct +/// inputs; [`shared_cases`] now asserts uniqueness so that cannot +/// recur silently. pub const SENTINEL: &str = "PMACS-CONFORMANCE-SENTINEL"; /// The canonical wording the helper uses for `ignored`. X3 emits it @@ -50,7 +58,8 @@ pub struct Case { pub expect: Outcome, } -/// A `/bin/sh` stub reproducing one case, sentinel included. +/// A `/bin/sh` stub reproducing one case exactly: its stdout bytes, its +/// own stderr payload, and its status. #[must_use] pub fn stub_script(case: &Case) -> String { let octal = case.stdout.iter().fold(String::new(), |mut acc, b| { @@ -185,5 +194,20 @@ pub fn shared_cases() -> Vec { stderr: "unrelated chatter on stderr".to_owned(), expect: Outcome::Safe, }); + // The set must be 45 DISTINCT inputs, not merely 45 entries. A + // previous revision gave every case the same stderr, which silently + // collapsed X3 into `1/E/empty` and X4 into `0/V/safe/bare` — 45 + // entries, 43 inputs, and two framing-specified cases quietly not + // exercised. Asserted here rather than in each suite so no consumer + // can forget it. + let mut seen = std::collections::HashSet::new(); + for case in &out { + assert!( + seen.insert((case.status, case.stdout.clone(), case.stderr.clone())), + "duplicate conformance input at {}: (status, stdout, stderr) already present", + case.name + ); + } + assert_eq!(seen.len(), out.len(), "every case must be a distinct input"); out } diff --git a/tests/gpu_invocation_acceptance.rs b/tests/gpu_invocation_acceptance.rs index 7e86f44..314bfe5 100644 --- a/tests/gpu_invocation_acceptance.rs +++ b/tests/gpu_invocation_acceptance.rs @@ -289,9 +289,6 @@ mod crdt { /// surfaces it, a boundary failure must not. #[test] fn rd_precondition_validates_the_whole_conformance_set() { - // `self::` and NOT `crate::`: this file is ALSO compiled as a - // nested module of `gpu_initial_target_acceptance.rs`, where - // `crate::` is the outer test crate and has no `common`. // `super::` and NOT `crate::`: this file is ALSO compiled as a // nested module of `gpu_initial_target_acceptance.rs`, where // `crate::` is the outer test crate and has no `common`.