Files
2026-10-14 15:28:51 -03:00

73 lines
3.3 KiB
Rust

//! Reproducible random UTF-8 input safety and edit consistency checks.
use notescraft_vim::{Doc, Key, KeyCode, Outcome, Vim};
const SEQUENCES: usize = 20_000;
const KEYS_PER_SEQUENCE: usize = 32;
const INITIAL: &str = "é猫🙂 one\n\n[αβ](note.md) [[猫]]\n'quoted' {text}\n";
const CHARACTERS: &str = "hjklwbeWBE0^$gG{}%fFtT;,HMLdcy><~uUxXsSrRJpPoOAIaiCDYvV\"az_+*-/:?nN123456789é猫🙂 ()[]`'.";
fn random(seed: &mut u64) -> u64 {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
*seed
}
fn check_edits(before: &str, document: &Doc, outcome: &Outcome) {
assert_eq!(outcome.text_changed, before != document.text);
let mut reconstructed = before.to_owned();
for edit in &outcome.edits {
assert_eq!(reconstructed.get(edit.range.clone()), Some(edit.deleted.as_str()));
reconstructed.replace_range(edit.range.clone(), &edit.inserted);
}
assert_eq!(reconstructed, document.text, "host edits must reproduce document");
}
#[test]
fn twenty_thousand_random_unicode_sequences_never_panic() {
let mut seed = 0x1728_394a_5b6c_7d8e;
let characters: Vec<char> = CHARACTERS.chars().collect();
let boundaries: Vec<usize> = INITIAL.char_indices().map(|(offset, _)| offset).collect();
for sequence in 0..SEQUENCES {
let mut document = Doc::new(INITIAL.into()).expect("valid UTF-8");
let cursor_index = usize::try_from(random(&mut seed) % u64::try_from(boundaries.len()).expect("boundary count")).expect("bounded cursor index");
document.cursor = *boundaries.get(cursor_index).expect("bounded cursor");
let mut vim = Vim::new();
for step in 0..KEYS_PER_SEQUENCE {
let character_index = usize::try_from(random(&mut seed) % u64::try_from(characters.len()).expect("character count")).expect("bounded key index");
let character = *characters.get(character_index).expect("bounded key");
let code = match random(&mut seed) % 20 {
0 => KeyCode::Esc,
1 => KeyCode::Enter,
2 => KeyCode::Backspace,
3 => KeyCode::Tab,
4 => KeyCode::Left,
5 => KeyCode::Right,
6 => KeyCode::Up,
7 => KeyCode::Down,
8 => KeyCode::Home,
9 => KeyCode::End,
10 => KeyCode::PageUp,
11 => KeyCode::PageDown,
12 => KeyCode::F(1),
_ => KeyCode::Char(character),
};
let modifiers = random(&mut seed);
let key = Key { code, ctrl: modifiers.is_multiple_of(19), shift: modifiers.is_multiple_of(13), alt: modifiers.is_multiple_of(29) };
let before = document.text.clone();
let outcome = vim.handle(key, &mut document);
assert!(document.cursor <= document.text.len(), "sequence={sequence} step={step} key={key:?}");
assert!(document.text.is_char_boundary(document.cursor), "sequence={sequence} step={step} key={key:?}");
if let Some(anchor) = document.anchor {
assert!(anchor <= document.text.len());
assert!(document.text.is_char_boundary(anchor));
}
for range in &outcome.search {
assert!(document.text.get(range.clone()).is_some());
}
check_edits(&before, &document, &outcome);
}
}
}