test(sigint): assert the 45 inputs are DISTINCT, and stop claiming every stub carries the sentinel

Two closure gaps.

1. Both suites asserted only `cases.len() == 45`, so the exact
   45-entries-over-43-distinct-inputs regression could recur unnoticed
   --- the one where X3 collapsed into 1/E/empty and X4 into
   0/V/safe/bare, leaving two framing-specified cases silently
   unexercised. shared_cases() now asserts uniqueness over
   (status, stdout, stderr), inside the generator so no consumer can
   forget it. Verified by reverting both payloads to the sentinel: it
   fails naming X3.

2. Comments and ledger still said every stub emits the sentinel, which
   the explicit X3/X4 payloads had made false. They now say the
   BRANCH-DISCRIMINATING cases carry it while X3 and X4 deliberately
   carry their own --- X3 the canonical ignored wording with no token,
   X4 noise --- and that this is what makes them distinct inputs. The
   duplicated `self::`/`super::` explanation left over from the nesting
   fix is reduced to the correct one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-20 14:22:12 +02:00
parent fb8a904923
commit b492426c69
No known key found for this signature in database
3 changed files with 39 additions and 11 deletions

View File

@ -290,8 +290,9 @@ from #171 and #215.
the shell needs them. the shell needs them.
- **Conformance vectors live in `tests/common/sigint_conformance.rs`** - **Conformance vectors live in `tests/common/sigint_conformance.rs`**
and are consumed by BOTH validators, so the two copies cannot drift and are consumed by BOTH validators, so the two copies cannot drift
while each still reports "45 cases". Every stub emits a **sentinel on while each still reports "45 cases". The **branch-discriminating** cases emit a
stderr**, which is what separates `ValidatedError` from `Boundary` — sentinel on stderr, which is what separates `ValidatedError` from
`Boundary` —
they share exit 2, so comparing codes alone let a validator that they share exit 2, so comparing codes alone let a validator that
accepted every status 2 pass the whole matrix. An `Outcome` enum accepted every status 2 pass the whole matrix. An `Outcome` enum
(Safe / ValidatedIgnored / ValidatedError / Boundary) is asserted (Safe / ValidatedIgnored / ValidatedError / Boundary) is asserted
@ -319,9 +320,15 @@ from #171 and #215.
`gate_validates_the_whole_shared_conformance_set`; token-to-stderr `gate_validates_the_whole_shared_conformance_set`; token-to-stderr
fails `sigint_helper_reports_safe_…`. An earlier entry said the fails `sigint_helper_reports_safe_…`. An earlier entry said the
status-2 mutation was caught only by the dedicated row — that was status-2 mutation was caught only by the dedicated row — that was
true of the pre-sentinel matrix and is **superseded**: every stub now true of the pre-sentinel matrix and is **superseded**: the
emits a sentinel on stderr, so the matrix can see which branch branch-discriminating cases now carry a sentinel, so the matrix can
produced the exit 2. 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:** - **Two PRE-EXISTING crdt-only failures found while gating properly:**
`m4_24_bare_string_glob_stays_relative` and `m4_24_bare_string_glob_stays_relative` and
`m4_24_d3_fallback_base_is_the_smallest_attachment_dir`. They `m4_24_d3_fallback_base_is_the_smallest_attachment_dir`. They

View File

@ -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_IGNORED: &[u8] = b"pmacs-sigint-v1:ignored";
pub const TOKEN_ERROR: &[u8] = b"pmacs-sigint-v1:error"; pub const TOKEN_ERROR: &[u8] = b"pmacs-sigint-v1:error";
/// Emitted on stderr by **every** stub, so a consumer's output can be /// Emitted on stderr by the **branch-discriminating** cases — the
/// searched for it: present ⇒ the child's stderr was surfaced. /// 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"; pub const SENTINEL: &str = "PMACS-CONFORMANCE-SENTINEL";
/// The canonical wording the helper uses for `ignored`. X3 emits it /// The canonical wording the helper uses for `ignored`. X3 emits it
@ -50,7 +58,8 @@ pub struct Case {
pub expect: Outcome, 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] #[must_use]
pub fn stub_script(case: &Case) -> String { pub fn stub_script(case: &Case) -> String {
let octal = case.stdout.iter().fold(String::new(), |mut acc, b| { let octal = case.stdout.iter().fold(String::new(), |mut acc, b| {
@ -185,5 +194,20 @@ pub fn shared_cases() -> Vec<Case> {
stderr: "unrelated chatter on stderr".to_owned(), stderr: "unrelated chatter on stderr".to_owned(),
expect: Outcome::Safe, 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 out
} }

View File

@ -289,9 +289,6 @@ mod crdt {
/// surfaces it, a boundary failure must not. /// surfaces it, a boundary failure must not.
#[test] #[test]
fn rd_precondition_validates_the_whole_conformance_set() { 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 // `super::` and NOT `crate::`: this file is ALSO compiled as a
// nested module of `gpu_initial_target_acceptance.rs`, where // nested module of `gpu_initial_target_acceptance.rs`, where
// `crate::` is the outer test crate and has no `common`. // `crate::` is the outer test crate and has no `common`.