Skip to content

ci: defend the Windows build and give the suite a Windows runner to ask - #275

Merged
vyncint merged 4 commits into
mainfrom
windows-cfg
Sep 8, 2026
Merged

ci: defend the Windows build and give the suite a Windows runner to ask#275
vyncint merged 4 commits into
mainfrom
windows-cfg

Conversation

@vyncint

@vyncint vyncint commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Refs #149 — steps 2 and 4 of the plan there, and the instrument for step 3. Step 1 (#249) is untouched and is what stands between this and a required Windows leg.

What this is

The Windows build compiles today and nothing defended it. Cross-checked from Linux: every workspace target builds for x86_64-pc-windows-msvc except tests/process.rs, which needed three #[cfg(unix)] gates. So:

  • windows-check (ci.yml, required-green): cargo clippy --workspace --all-targets --all-features --target x86_64-pc-windows-msvc -- -D warnings on ubuntu-latest — no Windows runner. check/clippy need only the target's std. ~40s. Listed in CONTRIBUTING §1 for gates-listed.
  • windows.yml — the Windows machine the project does not have. workflow_dispatch against any ref; runs the whole suite with --no-fail-fast (non-blocking — it is red on /bin/sh until The suite drives /bin/sh at 98 sites, so every test depends on the host shell and none can run where there is none #249), then the probe below, and puts the pass count, failed-test list and probe table in the job summary. Logs are the artifact.
  • tests/conpty_probe.rs#[ignore]d diagnostic. Sends 21 escape sequences through the PTY layer via the platform's own file printer (cat / cmd /d /c type, no shell quoting) and prints per sequence whether the master read exactly what the child wrote. On Unix: 21/21 verbatim, which is what makes the probe itself testable here. On Windows the ABSENT rows are the step-3 findings: DA1/DSR/OSC 11 absent means ConPTY answered the child itself and Graphics::Sixel/background_rgb/cell_size never reach it; 2026 absent means wait_frame cannot be honest there.

Step 2, the honest cfg:

  • PTY_LIFECYCLE is #[cfg(unix)]; pty_lifecycle_guard() returns Option<MutexGuard>None where there is no device table to race on. Linux keeps the lock deliberately: its cost is unmeasurable against a spawn, and dropping it there is a behaviour change the stress workflow would need to bless, not a cleanup. Flagging that as the one judgement call.
  • fixture_bin looks for <name><EXE_SUFFIX> — it asserted on target/debug/hello-tui and would have failed on Windows before any test ran.
  • Terminal::signal was already gated at definition and re-export; only the test file and one doctest lagged.
  • TerminalBuilder::validate judged "bare program name" by "contains no /" — D:\a\…\hello-tui.exe contains none, so bin! was refused under env_clear before spawning. Now "has no directory part", asked of the path.

What the three runs on this branch measured

Written up in full as the step-3 comment on #149; the short form:

  • Suite on windows-latest (run 3): 274 passed, 163 failed. Unit tests 210/210. 121 of the failures name a shell — The suite drives /bin/sh at 98 sites, so every test depends on the host shell and none can run where there is none #249. The rest are ConPTY, and they match the probe row for row.
  • Probe: 21 sequences in, 6 out verbatim. Eaten outright: DA1, OSC 11, XTGETTCAP, mouse modes, focus, kitty, sixel, HTS/TBC. Rewritten but equivalent: SGR, OSC 2→0, DEC Special Graphics→UTF-8, tab→CUF, OSC 8 with a ConPTY-synthesized id. DEC 2026 arrives reordered — the bracket closes before the content — which is why every wait_frame test times out with frames observed but never matching.
  • Every child gets a preamble: \e[6n \e[?9001h \e[?1004h \e[m \e]0;<exe>\a \e[?25h. The 6n is INHERIT_CURSOR asking the host where the cursor is, and the child does not start until it is answered — termlens's responder does, which is why the harness runs there at all; the probe's first version only read, and hung (run 1). The 1004h is why focus_events() is true from byte one on Windows.

Two Windows bugs in termlens itself came out of it and are fixed here (bin! refused an absolute D:\…\x.exe as bare; the ExitStatus::signal doctest did not compile off Unix). Run 3 shows both bin! tests passing on Windows.

The step-3 decision this supports: Windows — screen assertions yes, frame assertions no, with the eaten list above as the documented #[cfg_attr(windows, ignore)] set once #249 lets the leg run.

Not in this PR

The crate cross-compiles for Windows today — every workspace target but
`tests/process.rs`, which needed three `#[cfg(unix)]` gates — and
nothing ran the compiler to prove it, so the build was rotting unwatched.
`windows-check` now runs the cross-target clippy on every PR from a Linux
runner: `cargo check`/`clippy` need only the target's std, which rustup
installs anywhere. Linking and running are what need the OS.

Those get `windows.yml`, dispatched on demand against any ref: the whole
suite with nothing stopping early, then `tests/conpty_probe.rs`, which
sends twenty-one escape sequences through the PTY layer and prints, per
sequence, whether the master read what the child wrote. ConPTY renders
and re-emits rather than forwards, and which sequences survive decides
which features termlens can honestly claim on Windows (#149, step 3).
On Unix the probe prints 21/21 verbatim, which is what lets it be
developed without a Windows machine.

Step 2 of #149 alongside: the PTY lifecycle lock is a no-op where there
is no device table to race on, and `fixture_bin` looks for the artifact
Cargo actually produced — `.exe` on Windows, nothing elsewhere.

Refs #149

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint vyncint added this to the v0.10 milestone Sep 8, 2026
…nd each case

The first Windows run of the probe hung on its first case. ConPTY is
created with PSEUDOCONSOLE_INHERIT_CURSOR, and a console so created asks
its host terminal `CSI 6 n` before it starts the child, then waits for
the answer. termlens's responder answers that as it would any query,
which is why the harness itself runs there; a probe that only read did
not. The reader now replies `CSI 1 ; 1 R` to every cursor-position query
it sees, and each case gets twenty seconds before it is reported as HUNG
and the table moves on — a console waiting for something it was never
told is a finding, not a reason to lose the other twenty rows.

The control case now prints what it read whatever its verdict: what the
console wraps around five plain bytes is the shape of everything else it
emits.

Refs #149

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
`bin!` failed on Windows before any test ran: under env_clear the builder
refused `D:\a\termlens\target\debug\hello-tui.exe` as "a bare program
name", because bare was "contains no `/`". It is now "has no directory
part", asked of the path — the same question on every platform, and the
one the error message was always describing.

The `ExitStatus::signal` doctest is Unix-only, since `termlens::Signal`
is; it compiled nowhere else. The doc says so now instead of failing.

Refs #149

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
The push trigger was for developing it on this branch; three runs later
it has done that job. Dispatch against any ref is the shape it keeps.

Refs #149

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint marked this pull request as ready for review September 8, 2026 01:54
@vyncint
vyncint merged commit 4db24cf into main Sep 8, 2026
14 checks passed
@vyncint
vyncint deleted the windows-cfg branch September 8, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant