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.
This commit is contained in:
l0ng-ai
2026-08-23 08:58:50 +08:00
parent ab8faf5877
commit d6de373619
@@ -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"
);
}