Compare commits

..

4 Commits

Author SHA1 Message Date
Matthew Gordon 2b959e5a68 Do some automated random testing of Rope 2024-11-09 11:35:27 -04:00
Matthew Gordon 454e2f12fe Make rope module directory
Replace `rope.rs` with `rope/mod.rs` and `rope/tests/mod.rs` in
preparation for adding more elaborate testing for `Rope`.
2024-11-08 22:02:23 -04:00
Matthew Gordon c2f71aac22 Add Rope::iter_chars_with_point() 2024-10-21 21:37:36 -03:00
Matthew Gordon f92e86601e Move ged-core into workspace 2024-10-21 12:54:30 -03:00
8 changed files with 712 additions and 279 deletions

View File

@ -1,7 +1,3 @@
[package]
name = "ged"
version = "0.1.0"
edition = "2021"
[dev-dependencies]
ntest = "0.9.3"
[workspace]
resolver = "2"
members = ["core"]

8
core/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "ged-core"
version = "0.1.0"
edition = "2021"
[dev-dependencies]
ntest = "0.9.3"
rand = {version="0.8.5", features=["small_rng"]}

View File

@ -1,4 +1,5 @@
use {super::fibbonacci::fibbonacci, std::rc::Rc};
use super::fibbonacci::fibbonacci;
pub use std::rc::Rc;
/// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure
/// implementation.
@ -213,6 +214,12 @@ impl Rope {
CharIterator::new(self)
}
/// Returns an iterator over the chars of the text which also contains the
/// current line and column.
pub fn iter_chars_with_point(self: &Rc<Self>) -> CharWithPointIterator {
CharWithPointIterator::new(self)
}
/// Concatenate two Ropes without rebalancing.
///
/// Combines to ropes by creating a new parent node and adding `left` and
@ -283,20 +290,44 @@ impl Iterator for NodeIterator {
}
}
pub struct CharIterator {
node_iterator: NodeIterator,
current_text: Option<String>,
str_index: usize,
}
pub struct CharIterator(CharWithPointIterator);
impl CharIterator {
fn new(rope: &Rc<Rope>) -> Self {
CharIterator(CharWithPointIterator::new(rope))
}
}
impl Iterator for CharIterator {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self {
CharIterator(inner) => inner.next().map(|p| p.character)
}
}
}
pub struct CharWithPointIterator {
node_iterator: NodeIterator,
current_text: Option<String>,
str_index: usize,
line_num: usize,
char_num: usize,
eol: bool,
}
impl CharWithPointIterator {
fn new(rope: &Rc<Rope>) -> Self {
let mut node_iterator = NodeIterator::new(rope.clone());
let current_text = Self::next_string(&mut node_iterator);
CharIterator {
CharWithPointIterator {
node_iterator,
current_text,
str_index: 0,
char_num: 0,
line_num: 0,
eol: false,
}
}
@ -309,18 +340,41 @@ impl CharIterator {
}
})
}
pub fn get_point(&self) -> (usize, usize) {
(self.line_num, self.char_num)
}
}
impl Iterator for CharIterator {
type Item = char;
pub struct CharWithPoint {
pub character: char,
pub line: usize,
pub column: usize,
}
impl Iterator for CharWithPointIterator {
type Item = CharWithPoint;
fn next(&mut self) -> Option<Self::Item> {
self.current_text
.as_ref()
.and_then(|text| {
let c = text.chars().nth(self.str_index);
let next_char = text.chars().nth(self.str_index);
self.str_index += 1;
c
if let Some(c) = next_char {
if self.eol {
self.char_num = 1;
self.line_num += 1;
} else {
self.char_num += 1
}
self.eol = c == '\n';
}
next_char.map(|c| CharWithPoint {
character: c,
line: self.line_num,
column: self.char_num,
})
})
.or_else(|| {
self.current_text = Self::next_string(&mut self.node_iterator);
@ -335,259 +389,4 @@ impl Iterator for CharIterator {
}
#[cfg(test)]
mod tests {
use super::*;
use ntest::timeout;
fn small_test_rope() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("abc"), Some(Rope::new("def"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_leaf_strings() -> Vec<&'static str> {
vec![
"abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz",
]
}
fn small_test_rope_full_string() -> &'static str {
"abcdefghijklmnopqrstuvwxyz"
}
fn small_test_rope_with_multibyte_chars() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> {
vec![
"aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz",
]
}
fn small_test_rope_with_multibyte_chars_full_string() -> &'static str {
"aあbcdeえfghiいjklmnおopqrstuうvwxyz"
}
impl Rope {
fn unwrap_branch_left(&self) -> &Rc<Rope> {
match self {
Rope::Branch { left, .. } => left,
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
fn unwrap_branch_right(&self) -> Option<&Rc<Rope>> {
match self {
Rope::Branch { right, .. } => right.as_ref(),
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
}
#[test]
#[timeout(100)]
fn node_iterator_for_single_node_returns_node_and_only_node() {
let target = Rope::new("The");
let result: Vec<_> = target.iter_nodes().collect();
assert_eq!(result.len(), 1);
assert_eq!(result[0].total_bytes(), 3);
assert_eq!(result[0].total_chars(), 3);
assert_eq!(result[0].total_lines(), 1);
match result[0].as_ref() {
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
_ => panic!(),
}
}
#[test]
#[timeout(100)]
fn node_iterator_returns_nodes_in_correct_order() {
let strings = small_test_rope_leaf_strings();
let target = small_test_rope();
let result: Vec<_> = target.iter_nodes().take(26).collect();
assert_eq!(result.len(), 10);
for (node, string) in result.iter().zip(strings) {
match &node.as_ref() {
Rope::Leaf { text } => assert_eq!(text, string),
_ => panic!(),
}
}
}
#[test]
#[timeout(100)]
fn char_iterator_returns_chars_in_correct_order() {
let target = small_test_rope();
let result: Vec<_> = target.iter_chars().collect();
assert_eq!(result.len(), 26);
for (&target_char, expected_char) in
result.iter().zip(small_test_rope_full_string().chars())
{
assert_eq!(target_char, expected_char);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location() {
let target = small_test_rope();
let full_string = small_test_rope_full_string();
for i in 0..(full_string.chars().count()) {
let (first, second) = target.split_at_char_index(i);
let first_string: String = first.iter_chars().collect();
let second_string: String = second.iter_chars().collect();
assert_eq!(first_string, full_string[0..i]);
assert_eq!(second_string, full_string[i..]);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location_with_multibyte_chars() {
let target = small_test_rope_with_multibyte_chars();
let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string()
.chars()
.collect();
for i in 0..(expected_chars.len()) {
let (first, second) = target.split_at_char_index(i);
let string: String = first.iter_chars().collect();
let expected: String = expected_chars[0..i].iter().collect();
assert_eq!(string, expected);
let string: String = second.iter_chars().collect();
let expected: String = expected_chars[i..].iter().collect();
assert_eq!(string, expected);
}
}
#[test]
fn depths_have_correct_values() {
let target = small_test_rope_with_multibyte_chars();
assert_eq!(target.depth(), 4);
assert_eq!(target.unwrap_branch_left().depth(), 2);
assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
2
);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
}
#[test]
fn insert_at_char_index() {
let target = Rope::new("The brown dog");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
let rope1 = target.insert_at_char_index(4, "quick");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
let rope2 = rope1.insert_at_char_index(9, " ");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
assert_eq!(
rope2.iter_chars().collect::<String>(),
"The quick brown dog"
);
let rope3 =
rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox.");
assert_eq!(
rope3.iter_chars().collect::<String>(),
"The quick brown dog jumps over the lazy fox."
);
}
#[test]
fn delete_at_char_index() {
let target = Rope::new("The quick brown fox jumps over the lazy dog.");
let test = target.delete_at_char_index(10, 6);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "The quick fox jumps over the lazy dog.");
let test = target.delete_at_char_index(0, 4);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "quick brown fox jumps over the lazy dog.");
let test = target.delete_at_char_index("The quick brown fox jumps over the lazy dog.".len()-5, 5);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy");
}
}
mod tests;

View File

@ -0,0 +1,69 @@
use super::super::{Rc, Rope};
use rand::{
distributions::{Alphanumeric, DistString},
rngs::SmallRng,
Rng, SeedableRng,
};
#[derive(Clone, Debug)]
pub enum Command {
InsertAtCharIndex { index: usize, text: String },
DeleteAtCharIndex { index: usize, length: usize },
}
impl Command {
pub fn run(&self, rope: &Rc<Rope>) -> Rc<Rope> {
match self {
Command::InsertAtCharIndex { index, text } => rope.insert_at_char_index(*index, text),
Command::DeleteAtCharIndex { index, length } => {
rope.delete_at_char_index(*index, *length)
}
}
}
pub fn run_on_string(&self, input: String) -> String {
match self {
Command::InsertAtCharIndex { index, text } => input
.chars()
.take(*index)
.chain(text.chars())
.chain(input.chars().skip(*index))
.collect(),
Command::DeleteAtCharIndex { index, length } => input
.chars()
.enumerate()
.filter(|(i, _)| *i < *index || *i >= index + length)
.map(|(_, c)| c)
.collect(),
}
}
}
// TODO: Alphanumeric only samples ASCII
pub fn generate_random_edit_sequence_with_seed(
length: usize,
seed: u64,
) -> (String, Vec<(Command, String)>) {
let mut rng = SmallRng::seed_from_u64(seed);
let start_text_length = rng.gen_range(0..4000);
let start_text = Alphanumeric.sample_string(&mut rng, start_text_length);
let num_steps = rng.gen_range(0..1000);
let mut steps = Vec::with_capacity(num_steps);
let mut current_text = start_text.clone();
for i in 0..num_steps {
let current_text_length = current_text.len();
let command = if rng.gen_bool(0.7) || current_text_length > 0 {
let index = rng.gen_range(0..current_text_length);
let text_len = rng.gen_range(0..100);
let text = Alphanumeric.sample_string(&mut rng, text_len);
Command::InsertAtCharIndex { index, text }
} else {
let index = rng.gen_range(0..current_text_length-1);
let length = rng.gen_range(1..(current_text_length - index));
Command::DeleteAtCharIndex { index, length }
};
current_text = command.run_on_string(current_text);
steps.push((command.clone(), current_text.clone()));
}
(start_text, steps)
}

566
core/src/rope/tests/mod.rs Normal file
View File

@ -0,0 +1,566 @@
use super::*;
use ntest::timeout;
mod command_list;
fn small_test_rope() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("abc"), Some(Rope::new("def"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_leaf_strings() -> Vec<&'static str> {
vec![
"abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz",
]
}
fn small_test_rope_full_string() -> &'static str {
"abcdefghijklmnopqrstuvwxyz"
}
fn small_test_rope_with_multibyte_chars() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> {
vec![
"aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz",
]
}
fn small_test_rope_with_multibyte_chars_full_string() -> &'static str {
"aあbcdeえfghiいjklmnおopqrstuうvwxyz"
}
impl Rope {
fn unwrap_branch_left(&self) -> &Rc<Rope> {
match self {
Rope::Branch { left, .. } => left,
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
fn unwrap_branch_right(&self) -> Option<&Rc<Rope>> {
match self {
Rope::Branch { right, .. } => right.as_ref(),
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
}
#[test]
#[timeout(100)]
fn node_iterator_for_single_node_returns_node_and_only_node() {
let target = Rope::new("The");
let result: Vec<_> = target.iter_nodes().collect();
assert_eq!(result.len(), 1);
assert_eq!(result[0].total_bytes(), 3);
assert_eq!(result[0].total_chars(), 3);
assert_eq!(result[0].total_lines(), 1);
match result[0].as_ref() {
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
_ => panic!(),
}
}
#[test]
#[timeout(100)]
fn node_iterator_returns_nodes_in_correct_order() {
let strings = small_test_rope_leaf_strings();
let target = small_test_rope();
let result: Vec<_> = target.iter_nodes().take(26).collect();
assert_eq!(result.len(), 10);
for (node, string) in result.iter().zip(strings) {
match &node.as_ref() {
Rope::Leaf { text } => assert_eq!(text, string),
_ => panic!(),
}
}
}
#[test]
#[timeout(100)]
fn char_iterator_returns_chars_in_correct_order() {
let target = small_test_rope();
let result: Vec<_> = target.iter_chars().collect();
assert_eq!(result.len(), 26);
for (&target_char, expected_char) in result.iter().zip(small_test_rope_full_string().chars()) {
assert_eq!(target_char, expected_char);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location() {
let target = small_test_rope();
let full_string = small_test_rope_full_string();
for i in 0..(full_string.chars().count()) {
let (first, second) = target.split_at_char_index(i);
let first_string: String = first.iter_chars().collect();
let second_string: String = second.iter_chars().collect();
assert_eq!(first_string, full_string[0..i]);
assert_eq!(second_string, full_string[i..]);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location_with_multibyte_chars() {
let target = small_test_rope_with_multibyte_chars();
let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string()
.chars()
.collect();
for i in 0..(expected_chars.len()) {
let (first, second) = target.split_at_char_index(i);
let string: String = first.iter_chars().collect();
let expected: String = expected_chars[0..i].iter().collect();
assert_eq!(string, expected);
let string: String = second.iter_chars().collect();
let expected: String = expected_chars[i..].iter().collect();
assert_eq!(string, expected);
}
}
#[test]
fn depths_have_correct_values() {
let target = small_test_rope_with_multibyte_chars();
assert_eq!(target.depth(), 4);
assert_eq!(target.unwrap_branch_left().depth(), 2);
assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
2
);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
}
#[test]
fn insert_at_char_index() {
let target = Rope::new("The brown dog");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
let rope1 = target.insert_at_char_index(4, "quick");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
let rope2 = rope1.insert_at_char_index(9, " ");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
assert_eq!(
rope2.iter_chars().collect::<String>(),
"The quick brown dog"
);
let rope3 =
rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox.");
assert_eq!(
rope3.iter_chars().collect::<String>(),
"The quick brown dog jumps over the lazy fox."
);
}
#[test]
fn delete_at_char_index() {
let target = Rope::new("The quick brown fox jumps over the lazy dog.");
let test = target.delete_at_char_index(10, 6);
assert_eq!(
target.iter_chars().collect::<String>(),
"The quick brown fox jumps over the lazy dog."
);
assert_eq!(
test.iter_chars().collect::<String>(),
"The quick fox jumps over the lazy dog."
);
let test = target.delete_at_char_index(0, 4);
assert_eq!(
target.iter_chars().collect::<String>(),
"The quick brown fox jumps over the lazy dog."
);
assert_eq!(
test.iter_chars().collect::<String>(),
"quick brown fox jumps over the lazy dog."
);
let test =
target.delete_at_char_index("The quick brown fox jumps over the lazy dog.".len() - 5, 5);
assert_eq!(
target.iter_chars().collect::<String>(),
"The quick brown fox jumps over the lazy dog."
);
assert_eq!(
test.iter_chars().collect::<String>(),
"The quick brown fox jumps over the lazy"
);
}
#[test]
fn char_iterator_reports_correct_point() {
// Build a rope from fragments so we get a proper tree structure.
let target = Rope::new("This")
.concat(Rope::new(" is the first "))
.concat(Rope::new("line.\n"))
.concat(Rope::new("This is the "))
.concat(Rope::new("second line.\nThis is the third line."))
.concat(Rope::new("\n"))
.concat(Rope::new(
"This is the fourth line.\nThis is the fifth line.\nThis is ",
))
.concat(Rope::new("the"))
.concat(Rope::new(" sixth"))
.concat(Rope::new(" line."))
.concat(Rope::new("\nThis"))
.concat(Rope::new(" is the seventh line."));
let expected_values = vec![
('T', 0, 1),
('h', 0, 2),
('i', 0, 3),
('s', 0, 4),
(' ', 0, 5),
('i', 0, 6),
('s', 0, 7),
(' ', 0, 8),
('t', 0, 9),
('h', 0, 10),
('e', 0, 11),
(' ', 0, 12),
('f', 0, 13),
('i', 0, 14),
('r', 0, 15),
('s', 0, 16),
('t', 0, 17),
(' ', 0, 18),
('l', 0, 19),
('i', 0, 20),
('n', 0, 21),
('e', 0, 22),
('.', 0, 23),
('\n', 0, 24),
('T', 1, 1),
('h', 1, 2),
('i', 1, 3),
('s', 1, 4),
(' ', 1, 5),
('i', 1, 6),
('s', 1, 7),
(' ', 1, 8),
('t', 1, 9),
('h', 1, 10),
('e', 1, 11),
(' ', 1, 12),
('s', 1, 13),
('e', 1, 14),
('c', 1, 15),
('o', 1, 16),
('n', 1, 17),
('d', 1, 18),
(' ', 1, 19),
('l', 1, 20),
('i', 1, 21),
('n', 1, 22),
('e', 1, 23),
('.', 1, 24),
('\n', 1, 25),
('T', 2, 1),
('h', 2, 2),
('i', 2, 3),
('s', 2, 4),
(' ', 2, 5),
('i', 2, 6),
('s', 2, 7),
(' ', 2, 8),
('t', 2, 9),
('h', 2, 10),
('e', 2, 11),
(' ', 2, 12),
('t', 2, 13),
('h', 2, 14),
('i', 2, 15),
('r', 2, 16),
('d', 2, 17),
(' ', 2, 18),
('l', 2, 19),
('i', 2, 20),
('n', 2, 21),
('e', 2, 22),
('.', 2, 23),
('\n', 2, 24),
('T', 3, 1),
('h', 3, 2),
('i', 3, 3),
('s', 3, 4),
(' ', 3, 5),
('i', 3, 6),
('s', 3, 7),
(' ', 3, 8),
('t', 3, 9),
('h', 3, 10),
('e', 3, 11),
(' ', 3, 12),
('f', 3, 13),
('o', 3, 14),
('u', 3, 15),
('r', 3, 16),
('t', 3, 17),
('h', 3, 18),
(' ', 3, 19),
('l', 3, 20),
('i', 3, 21),
('n', 3, 22),
('e', 3, 23),
('.', 3, 24),
('\n', 3, 25),
('T', 4, 1),
('h', 4, 2),
('i', 4, 3),
('s', 4, 4),
(' ', 4, 5),
('i', 4, 6),
('s', 4, 7),
(' ', 4, 8),
('t', 4, 9),
('h', 4, 10),
('e', 4, 11),
(' ', 4, 12),
('f', 4, 13),
('i', 4, 14),
('f', 4, 15),
('t', 4, 16),
('h', 4, 17),
(' ', 4, 18),
('l', 4, 19),
('i', 4, 20),
('n', 4, 21),
('e', 4, 22),
('.', 4, 23),
('\n', 4, 24),
('T', 5, 1),
('h', 5, 2),
('i', 5, 3),
('s', 5, 4),
(' ', 5, 5),
('i', 5, 6),
('s', 5, 7),
(' ', 5, 8),
('t', 5, 9),
('h', 5, 10),
('e', 5, 11),
(' ', 5, 12),
('s', 5, 13),
('i', 5, 14),
('x', 5, 15),
('t', 5, 16),
('h', 5, 17),
(' ', 5, 18),
('l', 5, 19),
('i', 5, 20),
('n', 5, 21),
('e', 5, 22),
('.', 5, 23),
('\n', 5, 24),
('T', 6, 1),
('h', 6, 2),
('i', 6, 3),
('s', 6, 4),
(' ', 6, 5),
('i', 6, 6),
('s', 6, 7),
(' ', 6, 8),
('t', 6, 9),
('h', 6, 10),
('e', 6, 11),
(' ', 6, 12),
('s', 6, 13),
('e', 6, 14),
('v', 6, 15),
('e', 6, 16),
('n', 6, 17),
('t', 6, 18),
('h', 6, 19),
(' ', 6, 20),
('l', 6, 21),
('i', 6, 22),
('n', 6, 23),
('e', 6, 24),
('.', 6, 25),
('\n', 7, 26),
];
for (found, (expected_char, expected_line, expected_column)) in
target.iter_chars_with_point().zip(expected_values)
{
assert_eq!(found.character, expected_char);
assert_eq!(found.line, expected_line);
assert_eq!(found.column, expected_column);
}
}
// Run multiple tests with different seeds so that `cargo test` will run them in
// parallel.
// TODO: This could use a macro to generate the list of tests.
fn random_insertions_and_deletions_with_seed(seed: u64) {
let (start_text, edits) = command_list::generate_random_edit_sequence_with_seed(1000, seed);
let mut target = Rope::new(start_text);
for (command, expected_text) in edits {
println!("{:?}", command);
target = command.run(&target);
assert_eq!(expected_text, target.iter_chars().collect::<String>());
}
}
#[test]
fn random_insertions_and_deletions_1() {
random_insertions_and_deletions_with_seed(0x1234567890ab0001);
}
#[test]
fn random_insertions_and_deletions_2() {
random_insertions_and_deletions_with_seed(0x1234567890ab0002);
}
#[test]
fn random_insertions_and_deletions_3() {
random_insertions_and_deletions_with_seed(0x1234567890ab0003);
}
#[test]
fn random_insertions_and_deletions_4() {
random_insertions_and_deletions_with_seed(0x1234567890ab0004);
}
#[test]
fn random_insertions_and_deletions_5() {
random_insertions_and_deletions_with_seed(0x1234567890ab0005);
}
#[test]
fn random_insertions_and_deletions_6() {
random_insertions_and_deletions_with_seed(0x1234567890ab0006);
}
#[test]
fn random_insertions_and_deletions_7() {
random_insertions_and_deletions_with_seed(0x1234567890ab0007);
}
#[test]
fn random_insertions_and_deletions_8() {
random_insertions_and_deletions_with_seed(0x1234567890ab0008);
}
#[test]
fn random_insertions_and_deletions_9() {
random_insertions_and_deletions_with_seed(0x1234567890ab0009);
}
#[test]
fn random_insertions_and_deletions_10() {
random_insertions_and_deletions_with_seed(0x1234567890ab0010);
}
#[test]
fn random_insertions_and_deletions_11() {
random_insertions_and_deletions_with_seed(0x1234567890ab0011);
}
#[test]
fn random_insertions_and_deletions_12() {
random_insertions_and_deletions_with_seed(0x1234567890ab0012);
}
#[test]
fn random_insertions_and_deletions_13() {
random_insertions_and_deletions_with_seed(0x1234567890ab0013);
}
#[test]
fn random_insertions_and_deletions_14() {
random_insertions_and_deletions_with_seed(0x1234567890ab0014);
}
#[test]
fn random_insertions_and_deletions_15() {
random_insertions_and_deletions_with_seed(0x1234567890ab0015);
}
#[test]
fn random_insertions_and_deletions_16() {
random_insertions_and_deletions_with_seed(0x1234567890ab0016);
}

View File

@ -1,5 +0,0 @@
mod core;
fn main() {
println!("Hello, world!");
}