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.
- **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

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_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<Case> {
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
}

View File

@ -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`.