From d6de37361933d3504999438fccca6fd69afaaf7c Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 23 Aug 2026 08:58:50 +0800 Subject: [PATCH] test(pane): assert awkward text survives the pty and the ring unchanged The grid's own tests cover wide cells and combining marks, but they sit at the end of the chain: a pty, a ring and a snapshot on a socket come first, and nothing exercised those with anything but ASCII. Both spellings of an accented letter go in on purpose. A terminal must not normalise, so the test asserts not only that each form comes back, but that neither turned into the other. --- crates/tty7-server/tests/pane_send_input.rs | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/tty7-server/tests/pane_send_input.rs b/crates/tty7-server/tests/pane_send_input.rs index 49322b85..ed22070f 100644 --- a/crates/tty7-server/tests/pane_send_input.rs +++ b/crates/tty7-server/tests/pane_send_input.rs @@ -268,3 +268,71 @@ fn a_preempting_attach_closes_the_displaced_controller_and_drops_its_input() { second.kill().expect("kill the pane"); } + +/// What a pane prints comes back byte for byte, however awkward the text. +/// +/// The replay's own tests cover wide cells and combining marks at the grid +/// level, and they are the layer *after* this one: a pty, a ring, and a +/// snapshot on a socket sit between what a program writes and what any of them +/// see. Nothing exercised that chain with anything but ASCII. +/// +/// Both spellings of an accented letter are here on purpose. A terminal must +/// not normalise — `e` followed by U+0301 and a precomposed U+00E9 are +/// different byte strings, a program that wrote one and reads back the other +/// has been lied to, and Unicode normalisation is exactly the kind of +/// helpfulness a layer in this chain might think to add. +/// +/// The rest are the shapes that break naive width handling: a CJK pair that +/// occupies two cells each, an emoji, a regional-indicator flag that is two +/// code points and one glyph, and a ZWJ sequence that is three. +#[test] +fn awkward_text_survives_the_pty_and_the_ring_unchanged() { + let daemon = Daemon::start(); + let panes = daemon.panes(); + + // Written with explicit escapes so this file's own encoding cannot decide + // what is being asked for. + const DECOMPOSED: &str = "e\\314\\201"; + const PRECOMPOSED: &str = "\\303\\251"; + let script = format!( + "printf 'cjk:\\344\\275\\240\\345\\245\\275|\\n'; \ + printf 'dec:{DECOMPOSED}|\\n'; \ + printf 'pre:{PRECOMPOSED}|\\n'; \ + printf 'flag:\\360\\237\\207\\257\\360\\237\\207\\265|\\n'; \ + printf 'zwj:\\360\\237\\221\\250\\342\\200\\215\\360\\237\\222\\273|\\n'; \ + printf 'DONE\\n'" + ); + + let mut session = panes + .spawn(None, size(), Some(one_shot_shell(&script)), None, None) + .expect("spawn a pane that prints the corpus"); + session + .set_recv_timeout(Some(STREAM_WITHIN)) + .expect("bound the stream reads"); + let seen = collect_until(&mut session, b"DONE"); + + for (what, bytes) in [ + ("a CJK pair", &b"cjk:\xe4\xbd\xa0\xe5\xa5\xbd|"[..]), + ("a decomposed accent", &b"dec:e\xcc\x81|"[..]), + ("a precomposed accent", &b"pre:\xc3\xa9|"[..]), + ("a regional-indicator flag", &b"flag:\xf0\x9f\x87\xaf\xf0\x9f\x87\xb5|"[..]), + ("a ZWJ sequence", &b"zwj:\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x92\xbb|"[..]), + ] { + assert!( + windows_contain(&seen, bytes), + "{what} did not come back as it was written; saw {:?}", + String::from_utf8_lossy(&seen) + ); + } + + // And the two accents stayed different, which is the whole point of + // sending both: a normalising layer would make these identical. + assert!( + !windows_contain(&seen, b"dec:\xc3\xa9|"), + "the decomposed accent was normalised into a precomposed one" + ); + assert!( + !windows_contain(&seen, b"pre:e\xcc\x81|"), + "the precomposed accent was decomposed" + ); +}