test(vdisplay): give the 3.2 case eyes — a subscriber, and the probe that splits the two candidates

§5 3.2 reproduces on glass and the shipped fix is not sufficient: the panel stays dark after
teardown. Two candidates were left undistinguished by the first on-glass run — the adopted snapshot
was already poisoned, or the dark-desk backstop never fired — because the case had no way to tell
them apart. This adds that way.

**The subscriber.** The adoption arm and `restore_displays_ccd`'s backstop announce themselves ONLY
through `tracing`, and a bare `cargo test` harness installs no subscriber, so both events went
nowhere. `tracing-subscriber` becomes a DEV-dependency of pf-vdisplay (the shipped host's closure
through this crate is unchanged) and `init_test_tracing` wires it: `try_init` so a second live case
in the same binary is a no-op rather than a panic, `with_test_writer` so it interleaves with the
harness rather than racing `println!`, `RUST_LOG` still winning over the `debug` default.

`live_inplace_resize` gets it too — its comment claimed tracing-subscriber "is not a dep of this
crate, run the host binary for traced runs", which was the same blindness written down as a
limitation. It no longer holds.

**The probe is the actual discriminator, and it needs no logs at all.** Member 1's isolate is
INJECTED to fail, so nothing of ours deactivates anything there. Sampling `active_physicals()`
immediately after member 1's create therefore answers the question directly:

* empty  -> the arriving IddCx monitor took the desktop on its own. Every snapshot from that instant
  on records "panel off", so member 2's adopted snapshot is POISONED AT BIRTH and restoring it
  faithfully restores darkness. Adoption works; the snapshot SOURCE is the defect.
* non-empty -> poisoning is excluded and the break is downstream: no adoption line means teardown's
  restore was never gated on, a backstop line with a non-zero force-EXTEND rc means the remedy
  itself failed.

The failing assertion now reads that verdict out instead of asserting one cause, so the next on-glass
run reports which link broke rather than only that one did.

⚠️ Still UNRUN on glass — this is the instrument, not the measurement. Needs .173's CONSOLE session
(ssh is session 0 and CCD sees nothing there), and it can leave the desk dark: recover with
`SDC_USE_DATABASE_CURRENT|SDC_APPLY`, not EXTEND, which returns rc=31 against a single display.

Checked while writing this, and recorded so it is not re-derived: the "backstop is structurally
inert on a one-display box" theory does NOT survive. Both real call sites restore BEFORE the virtual
is REMOVEd, so two displays are connected and the preset applies — `live_force_extend_*` measured
exactly that (1 -> 1 -> 2). The documented residual is narrower: a restore that fails once the
virtual is already gone.

Verified: `xcheck.sh windows clippy` clean (it passes `--all-targets`, so this test code really is
compiled), `cargo fmt --all --check` clean.

⚠️ `Cargo.lock` moves with this commit and must not be split from it. CI runs
`cargo clippy --workspace --all-targets --locked`, so a new dependency edge whose lockfile entry is
missing does not degrade to a re-resolve — it FAILS the build outright. The dev-dependency adds one
line (pf-vdisplay gains a `tracing-subscriber` edge; the package itself was already in the graph via
punktfunk-host and pf-encode, so nothing new is vendored).
This commit is contained in:
2026-07-29 00:18:27 +02:00
parent 8381fd73ef
commit ed021a13ee
3 changed files with 60 additions and 7 deletions
Generated
+1
View File
@@ -3051,6 +3051,7 @@ dependencies = [
"sha2",
"tokio",
"tracing",
"tracing-subscriber",
"utoipa",
"wayland-backend",
"wayland-client",
+8
View File
@@ -33,6 +33,14 @@ utoipa = { version = "5", features = ["axum_extras"] }
sha2 = "0.10"
hex = "0.4"
[dev-dependencies]
# The `#[ignore]`d on-glass cases drive the manager/backend through code paths whose only account of
# what they chose is `tracing`. A bare test harness installs no subscriber, so those runs were blind:
# `live_a_failed_first_isolate_is_recovered_by_adopting_the_next` could see the panel stay dark but
# not whether the adoption arm fired, or whether the dark-desk backstop ran and failed. Dev-only, so
# the shipped host's dependency closure through this crate is unchanged.
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"
# The Mutter backend drives D-Bus RemoteDesktop + ScreenCast.RecordVirtual via ashpd on a tokio
@@ -1010,6 +1010,10 @@ mod tests {
#[test]
#[ignore = "needs the pf-vdisplay driver on real hardware; run with --ignored"]
fn live_a_failed_first_isolate_is_recovered_by_adopting_the_next() {
// Without this the run is BLIND: the adoption arm and the dark-desk backstop announce
// themselves only through `tracing`, and a bare test harness has no subscriber. The first
// on-glass run could see the panel stay dark but not say WHICH link broke.
init_test_tracing();
assert!(
std::env::var("PUNKTFUNK_NO_ISOLATE").is_err(),
"PUNKTFUNK_NO_ISOLATE forces Topology::Extend — this case needs Exclusive"
@@ -1036,10 +1040,19 @@ mod tests {
})
.expect("create member 1");
thread::sleep(Duration::from_secs(2));
// ⭐ THE MEASUREMENT THAT SEPARATES THE TWO CANDIDATES. Member 1's isolate was injected to
// fail, so nothing of OURS deactivated anything here. If the operator's panel is ALREADY
// dark at this point, the arriving IddCx monitor took the desktop on its own — and every
// snapshot taken from here on records "panel off", so member 2's adopted snapshot is
// POISONED AT BIRTH and restoring it faithfully restores darkness. If the panel is still
// lit here, poisoning is excluded and the failure is downstream (adoption never fired, or
// the restore/backstop did and could not re-light it).
let physicals_after_m1 = active_physicals();
println!(
"after member 1 (isolate INJECTED to fail): {:?}",
active_targets()
);
println!("physicals after member 1 : {physicals_after_m1:?} <- poisoned-at-birth probe");
let mut vd2 = PfVdisplayDisplay::new().expect("open pf-vdisplay (member 2)");
vd2.set_client_identity(Some([0xB2; 32]));
@@ -1075,11 +1088,20 @@ mod tests {
println!("physicals after teardown : {physicals_after:?}");
assert!(
!physicals_after.is_empty(),
"the operator's physical panel was left DEACTIVATED after teardown. The first \
member's isolate failed, so the group held no restore snapshot; the second member's \
isolate deactivated the physicals and its snapshot was discarded (sweep §5 3.2). \
Active targets now: {:?}",
active_targets()
"the operator's physical panel was left DEACTIVATED after teardown (sweep §5 3.2). \
Active targets now: {:?}.\n\
Which candidate this run implicates — read it off the poisoned-at-birth probe above:\n\
* physicals after member 1 was EMPTY ({m1_empty}) -> the snapshot member 2 adopted was \
already poisoned: the panel went dark at member 1's create (IddCx auto-activation), so \
the adopted topology records 'panel off' and restoring it faithfully restores darkness. \
Adoption is working; the SNAPSHOT SOURCE is the defect.\n\
* physicals after member 1 was NON-empty -> poisoning is excluded; the break is \
downstream. Check the trace for 'adopting this member's' (the adoption arm) and for \
'no external physical display active after the restore' (the dark-desk backstop). A \
missing adoption line means teardown's restore was never gated on; a backstop line \
followed by a non-zero force-EXTEND rc means the remedy itself failed.",
active_targets(),
m1_empty = physicals_after_m1.is_empty()
);
}
@@ -1130,6 +1152,27 @@ mod tests {
.collect()
}
/// Surface the manager/backend `tracing` output on stdout for a live case.
///
/// These on-glass cases drive decision points — the isolate ladder, the snapshot-adoption arm,
/// `restore_displays_ccd`'s dark-desk backstop — whose ONLY account of what they chose is a
/// `tracing` event. A bare `cargo test` harness installs no subscriber, so those events go
/// nowhere and a failing run cannot say which link broke; that is exactly what left §5 3.2's
/// two candidates undistinguished after the first on-glass run.
///
/// `with_test_writer` routes through the harness's capture, so the output appears under
/// `--nocapture` (and on failure) rather than racing `println!`. Idempotent and non-fatal: the
/// global default can only be set once per process, and several live cases may run in one
/// binary, so a second call is a no-op rather than a panic that would fail an unrelated test.
/// `RUST_LOG` still wins when set; the default is `debug` for our own crates, which is where
/// the ladder's reasoning lives.
fn init_test_tracing() {
use tracing_subscriber::{fmt, EnvFilter};
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("pf_vdisplay=debug,pf_win_display=debug"));
let _ = fmt().with_env_filter(filter).with_test_writer().try_init();
}
/// The active targets that are EXTERNAL PHYSICAL panels — the operator's actual desk.
fn active_physicals() -> Vec<(u32, String)> {
pf_win_display::win_display::target_inventory()
@@ -1213,8 +1256,9 @@ mod tests {
fn live_inplace_resize() {
// Live-run diagnostics: surface the manager/backend tracing (activation ladder, settle
// waits, UPDATE_MODES) on stdout — a bare test harness has no subscriber, which made the
// first on-glass run blind.
// (tracing-subscriber is not a dep of this crate — run the host binary for traced runs.)
// first on-glass run blind. `tracing-subscriber` is now a dev-dependency, so this case no
// longer has to be re-run through the host binary to be traced.
init_test_tracing();
// Context probe: can this process see the CCD active-path set at all? (`None` = the query
// itself fails in this session/window-station — the whole ladder would be blind, and a
// "monitor never activated" verdict would be an artifact of the test context.)