use rand::prelude::*;
use std::cmp::min;
use std::ops::Range;
use std::ptr;
use jumprope::JumpRope;
use jumprope::JumpRopeBuf;
const UNI_CHARS: [char; 24] = [
'\n', 'a', 'b', 'c', '1', '2', '3', ' ', '_', '©', '¥', '½', 'Ύ', 'Δ', 'δ', 'Ϡ', '←', '↯', '↻', '⇈', '𐆐', '𐆔', '𐆘', '𐆚', ];
fn random_unicode_string(len: usize, rng: &mut SmallRng) -> String {
let mut s = String::new();
for _ in 0..len {
s.push(UNI_CHARS[rng.gen_range(0 .. UNI_CHARS.len())] as char);
}
s
}
const ASCII_CHARS: &[u8; 83] = b" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()[]{}<>?,./";
#[allow(unused)]
fn random_ascii_string(len: usize, rng: &mut SmallRng) -> String {
let mut s = String::new();
for _ in 0..len {
s.push(ASCII_CHARS[rng.gen_range(0 .. ASCII_CHARS.len())] as char);
}
s
}
fn check(r: &JumpRope, expected: &str) {
r.check();
assert_eq!(r.to_string(), expected);
assert_eq!(r.len_bytes(), expected.len());
assert_eq!(r.len_chars(), expected.chars().count());
#[cfg(feature = "wchar_conversion")] {
assert_eq!(r.len_wchars(), expected.chars().map(|c| c.len_utf16()).sum());
assert_eq!(r.chars_to_wchars(r.len_chars()), r.len_wchars());
assert!(r.len_wchars() >= r.len_chars());
assert_eq!(r.wchars_to_chars(r.len_wchars()), r.len_chars());
}
assert_eq!(*r, JumpRope::from(expected), "Rope comparison fails");
let clone = r.clone();
clone.check();
assert_eq!(*r, clone, "Rope does not equal its clone");
}
#[test]
fn empty_rope_has_no_contents() {
let mut r = JumpRope::new();
check(&r, "");
r.insert(0, "");
check(&r, "");
}
#[test]
fn from_str_and_string() {
let r1 = JumpRope::from("hi");
check(&r1, "hi");
let r2 = JumpRope::from(String::from("hi"));
check(&r2, "hi");
}
#[test]
fn insert_at_location() {
let mut r = JumpRope::new();
r.insert(0, "AAA");
check(&r, "AAA");
r.insert(0, "BBB");
check(&r, "BBBAAA");
r.insert(6, "CCC");
check(&r, "BBBAAACCC");
r.insert(5, "DDD");
check(&r, "BBBAADDDACCC");
}
#[test]
fn new_string_has_content() {
let r = JumpRope::from("hi there");
check(&r, "hi there");
let mut r = JumpRope::from("κόσμε");
check(&r, "κόσμε");
r.insert(2, "𝕐𝕆😘");
check(&r, "κό𝕐𝕆😘σμε");
}
#[test]
fn del_at_location() {
let mut r = JumpRope::from("012345678");
check(&r, "012345678");
r.remove(8..9);
check(&r, "01234567");
r.remove(0..1);
check(&r, "1234567");
r.remove(5..6);
check(&r, "123457");
r.remove(5..6);
check(&r, "12345");
r.remove(0..5);
check(&r, "");
}
#[test]
fn del_past_end_of_string() {
let mut r = JumpRope::new();
r.remove(0..100);
check(&r, "");
r.insert(0, "hi there");
r.remove(3..13);
check(&r, "hi ");
}
#[test]
fn really_long_ascii_string() {
let mut rng = SmallRng::seed_from_u64(1234);
let len = 2000;
let s = random_ascii_string(len, &mut rng);
let mut r = JumpRope::from(s.as_str());
check(&r, s.as_str());
r.remove(1..len - 1);
let expect = format!("{}{}", s.chars().next().unwrap(), s.chars().rev().next().unwrap());
check(&r, expect.as_str());
}
fn string_insert_at(s: &mut String, char_pos: usize, contents: &str) {
let byte_pos = s.char_indices().skip(char_pos).next()
.map(|(p, _)| p).unwrap_or(s.len());
let old_len = s.len();
let new_bytes = contents.len();
for _ in 0..new_bytes { s.push('\0'); }
unsafe {
let bytes = s.as_mut_vec().as_mut_ptr();
ptr::copy(
bytes.offset(byte_pos as isize),
bytes.offset((byte_pos + new_bytes) as isize),
old_len - byte_pos
);
ptr::copy_nonoverlapping(
contents.as_ptr(),
bytes.offset(byte_pos as isize),
new_bytes
);
}
}
fn char_range_to_byte_range(s: &String, range: Range<usize>) -> Range<usize> {
let mut iter = s.char_indices().map(|(p, _)| p).skip(range.start).peekable();
let start = iter.peek().map_or_else(|| s.len(), |&p| p);
let mut iter = iter.skip(range.end - range.start).peekable();
let end = iter.peek().map_or_else(|| s.len(), |&p| p);
start..end
}
fn string_del_at(s: &mut String, pos: usize, length: usize) {
let byte_range = char_range_to_byte_range(s, pos..pos+length);
s.drain(byte_range);
}
fn random_edits(seed: u64, verbose: bool) {
let mut r = JumpRope::new();
let mut s = String::new();
let mut rng = SmallRng::seed_from_u64(seed);
for _i in 0..400 {
if verbose { println!("{_i} s: '{s}'"); }
let len = s.chars().count();
if len == 0 || (len < 1000 && rng.gen::<f32>() < 0.5) {
let pos = rng.gen_range(0..len+1);
let text = random_unicode_string(rng.gen_range(0..20), &mut rng);
if verbose {
println!("Inserting '{text}' at char {pos} (Byte length: {}, char len: {}, wchar len: {})",
text.len(), text.chars().count(),
text.chars().map(|c| c.len_utf16()).sum::<usize>()
);
}
r.insert(pos, text.as_str());
string_insert_at(&mut s, pos, text.as_str());
} else {
let pos = rng.gen_range(0..len);
let dlen = min(rng.gen_range(0..10), len - pos);
if verbose {
println!("Removing {dlen} characters at {pos}");
}
r.remove(pos..pos+dlen);
string_del_at(&mut s, pos, dlen);
}
if !cfg!(miri) {
check(&r, s.as_str());
}
}
if cfg!(miri) {
check(&r, s.as_str());
}
}
#[test]
fn fuzz_once() {
random_edits(10, false);
}
#[test]
#[ignore]
fn fuzz_forever() {
for seed in 0.. {
if seed % 100 == 0 { println!("seed: {seed}"); }
random_edits(seed, false);
}
}
#[cfg(feature = "wchar_conversion")]
fn random_edits_wchar(seed: u64, verbose: bool) {
let mut r = JumpRope::new();
let mut s = String::new();
let mut rng = SmallRng::seed_from_u64(seed);
for _i in 0..400 {
if verbose { println!("{_i} s: '{s}'"); }
let len_chars = s.chars().count();
if len_chars == 0 || (len_chars < 1000 && rng.gen::<f32>() < 0.5) {
let pos_chars = rng.gen_range(0..len_chars + 1);
let pos_wchar = s
.chars()
.take(pos_chars)
.map(|c| c.len_utf16())
.sum();
let text = random_unicode_string(rng.gen_range(0..20), &mut rng);
if verbose {
println!("Inserting '{text}' at char {pos_chars} / wchar {pos_wchar}");
println!("Byte length {} char len {} / wchar len {}",
text.len(), text.chars().count(), text.chars().map(|c| c.len_utf16()).sum::<usize>());
}
r.insert_at_wchar(pos_wchar, text.as_str());
string_insert_at(&mut s, pos_chars, text.as_str());
} else {
let pos_chars = rng.gen_range(0..len_chars);
let dlen_chars = min(rng.gen_range(0..10), len_chars - pos_chars);
let char_range = pos_chars..pos_chars+dlen_chars;
let byte_range = char_range_to_byte_range(&s, char_range.clone());
let start_wchar = s[..byte_range.start].chars().map(|c| c.len_utf16()).sum::<usize>();
let len_wchar = s[byte_range.clone()].chars().map(|c| c.len_utf16()).sum::<usize>();
let wchar_range = start_wchar..start_wchar + len_wchar;
if verbose {
println!("Removing {}..{} (wchar {}..{})",
char_range.start, char_range.end,
wchar_range.start, wchar_range.end
);
}
r.remove_at_wchar(wchar_range);
s.drain(byte_range);
}
if !cfg!(miri) {
check(&r, s.as_str());
}
}
}
#[cfg(feature = "wchar_conversion")]
#[test]
fn fuzz_wchar_once() {
random_edits_wchar(22, false);
}
#[cfg(feature = "wchar_conversion")]
#[test]
#[ignore]
fn fuzz_wchar_forever() {
for seed in 0.. {
if seed % 100 == 0 { println!("seed: {seed}"); }
random_edits_wchar(seed, false);
}
}
fn random_edits_buffered(seed: u64, verbose: bool) {
let mut r = JumpRopeBuf::new();
let mut s = String::new();
let mut rng = SmallRng::seed_from_u64(seed);
for _i in 0..400 {
if verbose { println!("{_i} s: '{s}'"); }
let len = s.chars().count();
if len == 0 || (len < 1000 && rng.gen::<f32>() < 0.5) {
let pos = rng.gen_range(0..len+1);
let text = random_unicode_string(rng.gen_range(0..20), &mut rng);
if verbose {
println!("Inserting '{text}' at char {pos} (Byte length: {}, char len: {}, wchar len: {})",
text.len(), text.chars().count(),
text.chars().map(|c| c.len_utf16()).sum::<usize>()
);
}
r.insert(pos, text.as_str());
string_insert_at(&mut s, pos, text.as_str());
} else {
let pos = rng.gen_range(0..len);
let dlen = min(rng.gen_range(0..10), len - pos);
if verbose {
println!("Removing {dlen} characters at {pos}");
}
r.remove(pos..pos+dlen);
string_del_at(&mut s, pos, dlen);
}
assert_eq!(r.is_empty(), s.is_empty());
if rng.gen_bool(0.05) {
assert_eq!(r.len_chars(), s.chars().count());
}
}
let rope = r.into_inner();
check(&rope, s.as_str());
}
#[test]
fn fuzz_buffered_once() {
random_edits_buffered(0, false);
}
#[test]
#[ignore]
fn fuzz_buffered_forever() {
for seed in 0.. {
if seed % 1000 == 0 { println!("seed: {seed}"); }
random_edits_buffered(seed, false);
}
}
#[test]
fn eq_variants() {
let rope = JumpRope::from("Hi there");
assert_eq!(rope.clone(), "Hi there");
assert_eq!(rope.clone(), String::from("Hi there"));
assert_eq!(rope.clone(), &String::from("Hi there"));
assert_eq!(&rope, "Hi there");
assert_eq!(&rope, String::from("Hi there"));
assert_eq!(&rope, &String::from("Hi there"));
}
#[test]
fn buffered_eq_variants() {
let rope = JumpRopeBuf::from("Hi there");
assert_eq!(rope.clone(), "Hi there");
assert_eq!(rope.clone(), String::from("Hi there"));
assert_eq!(rope.clone(), &String::from("Hi there"));
assert_eq!(&rope, "Hi there");
assert_eq!(&rope, String::from("Hi there"));
assert_eq!(&rope, &String::from("Hi there"));
}