151 lines
4.4 KiB
Rust
151 lines
4.4 KiB
Rust
//! Tests for `bxctl::escape`: text the model wrote is printed as data. Do not edit.
|
|
//!
|
|
//! The expected escapes are built by `esc`, never spelled out, so that nothing that handles this
|
|
//! file can turn one into the character it stands for.
|
|
|
|
use bxctl::escape::{escape_json_text, escape_model_text};
|
|
|
|
const BACKSLASH: char = '\\';
|
|
|
|
/// The escape for one code point: a backslash, `u`, and four lowercase hex digits.
|
|
fn esc(code: u32) -> String {
|
|
format!("{BACKSLASH}u{code:04x}")
|
|
}
|
|
|
|
fn ch(code: u32) -> char {
|
|
char::from_u32(code).unwrap()
|
|
}
|
|
|
|
/// Every code point that must be escaped, as inclusive ranges.
|
|
const HIDDEN: [(u32, u32); 6] = [
|
|
(0x0000, 0x001f),
|
|
(0x007f, 0x009f),
|
|
(0x200b, 0x200f),
|
|
(0x2028, 0x202e),
|
|
(0x2060, 0x2069),
|
|
(0xfeff, 0xfeff),
|
|
];
|
|
|
|
fn hidden(code: u32) -> bool {
|
|
HIDDEN.iter().any(|(lo, hi)| (*lo..=*hi).contains(&code))
|
|
}
|
|
|
|
/// Walks every code point below U+11000, not a sample: each is either escaped exactly or left
|
|
/// exactly as it is.
|
|
#[test]
|
|
fn every_listed_code_point_is_escaped_and_no_other() {
|
|
let mut escaped = 0;
|
|
for code in 0..0x11000u32 {
|
|
let Some(c) = char::from_u32(code) else {
|
|
continue; // the surrogates are not characters
|
|
};
|
|
let text = format!("a{c}b");
|
|
let got = escape_json_text(&text);
|
|
if hidden(code) {
|
|
escaped += 1;
|
|
assert_eq!(got, format!("a{}b", esc(code)), "U+{code:04X}");
|
|
} else {
|
|
assert_eq!(got, text, "U+{code:04X} must pass through");
|
|
}
|
|
}
|
|
assert_eq!(escaped, 32 + 33 + 5 + 7 + 10 + 1, "the six ranges, in full");
|
|
}
|
|
|
|
#[test]
|
|
fn the_edges_of_each_range() {
|
|
for (lo, hi) in HIDDEN {
|
|
assert_eq!(escape_json_text(&ch(lo).to_string()), esc(lo));
|
|
assert_eq!(escape_json_text(&ch(hi).to_string()), esc(hi));
|
|
if lo > 0 {
|
|
let before = ch(lo - 1).to_string();
|
|
assert_eq!(escape_json_text(&before), before, "U+{:04X}", lo - 1);
|
|
}
|
|
let after = ch(hi + 1).to_string();
|
|
assert_eq!(escape_json_text(&after), after, "U+{:04X}", hi + 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn hex_digits_are_lowercase_and_there_are_always_four() {
|
|
assert_eq!(escape_json_text("\x1b"), esc(0x1b));
|
|
assert!(escape_json_text("\x1b").ends_with("001b"));
|
|
assert!(escape_json_text("\0").ends_with("0000"));
|
|
assert!(escape_json_text(&ch(0xfeff).to_string()).ends_with("feff"));
|
|
assert!(escape_json_text(&ch(0x202e).to_string()).ends_with("202e"));
|
|
}
|
|
|
|
#[test]
|
|
fn an_escape_sequence_cannot_reach_the_terminal() {
|
|
let text = "before\x1b[8mhidden\x1b[0m\x07after";
|
|
let got = escape_json_text(text);
|
|
assert!(!got.contains('\x1b') && !got.contains('\x07'), "{got:?}");
|
|
assert_eq!(
|
|
got,
|
|
format!(
|
|
"before{}[8mhidden{}[0m{}after",
|
|
esc(0x1b),
|
|
esc(0x1b),
|
|
esc(0x07)
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_path_cannot_be_shown_backwards() {
|
|
// U+202E makes a terminal draw what follows from right to left.
|
|
let text = format!("/home/kyle/notes/{}dm.terces", ch(0x202e));
|
|
assert_eq!(
|
|
escape_json_text(&text),
|
|
format!("/home/kyle/notes/{}dm.terces", esc(0x202e))
|
|
);
|
|
let text = format!("a{}b{}c", ch(0x200b), ch(0x2066));
|
|
assert_eq!(
|
|
escape_json_text(&text),
|
|
format!("a{}b{}c", esc(0x200b), esc(0x2066))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ordinary_text_is_unchanged() {
|
|
for text in [
|
|
"",
|
|
"plain",
|
|
r#"{"command":"ls -l","cwd":"/home/kyle"}"#,
|
|
"naïve café 日本語 🙂",
|
|
"a backslash \\ and a quote \" stay as they are",
|
|
] {
|
|
assert_eq!(escape_json_text(text), text);
|
|
assert_eq!(escape_model_text(text), text);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn json_text_escapes_newline_and_tab_but_model_text_keeps_them() {
|
|
let text = "one\n\ttwo\r\n";
|
|
assert_eq!(
|
|
escape_json_text(text),
|
|
format!("one{}{}two{}{}", esc(0x0a), esc(0x09), esc(0x0d), esc(0x0a))
|
|
);
|
|
assert_eq!(
|
|
escape_model_text(text),
|
|
format!("one\n\ttwo{}\n", esc(0x0d)),
|
|
"only newline and tab pass; a carriage return could overwrite the line"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn model_text_escapes_everything_else_the_same_way() {
|
|
for (lo, hi) in HIDDEN {
|
|
for code in lo..=hi {
|
|
if code == 0x0a || code == 0x09 {
|
|
continue;
|
|
}
|
|
assert_eq!(
|
|
escape_model_text(&ch(code).to_string()),
|
|
esc(code),
|
|
"U+{code:04X}"
|
|
);
|
|
}
|
|
}
|
|
}
|