From 3d98744c2a929c35852d20b5724ef5b6be1e3312 Mon Sep 17 00:00:00 2001 From: Bernardo Date: Wed, 12 Dec 2018 18:51:43 +0100 Subject: proptest strategies for TextUnit and AtomTextEdit --- crates/ra_text_edit/src/lib.rs | 2 + crates/ra_text_edit/src/test_utils.rs | 114 ++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 crates/ra_text_edit/src/test_utils.rs (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 13e20f6fb..0a407795e 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -1,5 +1,7 @@ mod text_edit; pub mod text_utils; +#[cfg(test)] +pub mod test_utils; pub use crate::text_edit::{TextEdit, TextEditBuilder}; diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs new file mode 100644 index 000000000..92b79ab59 --- /dev/null +++ b/crates/ra_text_edit/src/test_utils.rs @@ -0,0 +1,114 @@ +use proptest::{prelude::*, proptest, proptest_helper}; +use text_unit::{TextUnit, TextRange}; +use crate::AtomTextEdit; + +pub fn arb_text() -> proptest::string::RegexGeneratorStrategy { + // generate multiple newlines + proptest::string::string_regex("(.*\n?)*").unwrap() +} + +fn text_offsets(text: &str) -> Vec { + text.char_indices() + .map(|(i, _)| TextUnit::from_usize(i)) + .collect() +} + +pub fn arb_offset(text: &str) -> BoxedStrategy { + let offsets = text_offsets(text); + // this is necessary to avoid "Uniform::new called with `low >= high`" panic + if offsets.is_empty() { + Just(TextUnit::from(0)).boxed() + } else { + prop::sample::select(offsets).boxed() + } +} + +pub fn arb_edits(text: &str) -> BoxedStrategy> { + let offsets = text_offsets(text); + let offsets_len = offsets.len(); + + if offsets_len == 0 { + return proptest::bool::ANY + .prop_flat_map(|b| { + // only valid edits + if b { + arb_text() + .prop_map(|text| vec![AtomTextEdit::insert(TextUnit::from(0), text)]) + .boxed() + } else { + Just(vec![]).boxed() + } + }) + .boxed(); + } + + proptest::sample::subsequence(offsets, 0..offsets_len) + .prop_flat_map(|xs| { + let strategies: Vec<_> = xs + .chunks(2) + .map(|chunk| match chunk { + &[from, to] => { + let range = TextRange::from_to(from, to); + (proptest::bool::ANY) + .prop_flat_map(move |b| { + if b { + Just(AtomTextEdit::delete(range)).boxed() + } else { + arb_text() + .prop_map(move |text| AtomTextEdit::replace(range, text)) + .boxed() + } + }) + .boxed() + } + &[x] => arb_text() + .prop_map(move |text| AtomTextEdit::insert(x, text)) + .boxed(), + _ => unreachable!(), + }) + .collect(); + strategies + }) + .boxed() +} + +fn arb_text_with_edits() -> BoxedStrategy<(String, Vec)> { + let text = arb_text(); + text.prop_flat_map(|s| { + let edits = arb_edits(&s); + (Just(s), edits) + }) + .boxed() +} + +fn intersect(r1: TextRange, r2: TextRange) -> Option { + let start = r1.start().max(r2.start()); + let end = r1.end().min(r2.end()); + if start <= end { + Some(TextRange::from_to(start, end)) + } else { + None + } +} + +proptest! { +#[test] + fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) { + proptest_atom_text_edits_are_valid(text, edits) + } +} + +fn proptest_atom_text_edits_are_valid(text: String, edits: Vec) { + // slicing doesn't panic + for e in &edits { + let _ = &text[e.delete]; + } + // ranges do not overlap + for (i1, e1) in edits.iter().skip(1).enumerate() { + for e2 in &edits[0..i1] { + if intersect(e1.delete, e2.delete).is_some() { + assert!(false, "Overlapping ranges {} {}", e1.delete, e2.delete); + } + } + } +} -- cgit v1.2.3 From dee426e1b120cd12fc608df77f977d4800b6655d Mon Sep 17 00:00:00 2001 From: Bernardo Date: Wed, 12 Dec 2018 19:25:50 +0100 Subject: simplify and reduce number of values explored --- crates/ra_text_edit/src/test_utils.rs | 52 ++++++++++++++++------------------- 1 file changed, 23 insertions(+), 29 deletions(-) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs index 92b79ab59..29301bff3 100644 --- a/crates/ra_text_edit/src/test_utils.rs +++ b/crates/ra_text_edit/src/test_utils.rs @@ -24,41 +24,35 @@ pub fn arb_offset(text: &str) -> BoxedStrategy { } pub fn arb_edits(text: &str) -> BoxedStrategy> { - let offsets = text_offsets(text); - let offsets_len = offsets.len(); - - if offsets_len == 0 { - return proptest::bool::ANY - .prop_flat_map(|b| { - // only valid edits - if b { - arb_text() - .prop_map(|text| vec![AtomTextEdit::insert(TextUnit::from(0), text)]) - .boxed() - } else { - Just(vec![]).boxed() - } - }) + if text.is_empty() { + // only valid edits + return Just(vec![]) + .boxed() + .prop_union( + arb_text() + .prop_map(|text| vec![AtomTextEdit::insert(TextUnit::from(0), text)]) + .boxed(), + ) .boxed(); } - proptest::sample::subsequence(offsets, 0..offsets_len) - .prop_flat_map(|xs| { - let strategies: Vec<_> = xs + let offsets = text_offsets(text); + let max_cuts = offsets.len().min(7); + + proptest::sample::subsequence(offsets, 0..max_cuts) + .prop_flat_map(|cuts| { + let strategies: Vec<_> = cuts .chunks(2) .map(|chunk| match chunk { &[from, to] => { let range = TextRange::from_to(from, to); - (proptest::bool::ANY) - .prop_flat_map(move |b| { - if b { - Just(AtomTextEdit::delete(range)).boxed() - } else { - arb_text() - .prop_map(move |text| AtomTextEdit::replace(range, text)) - .boxed() - } - }) + Just(AtomTextEdit::delete(range)) + .boxed() + .prop_union( + arb_text() + .prop_map(move |text| AtomTextEdit::replace(range, text)) + .boxed(), + ) .boxed() } &[x] => arb_text() @@ -92,7 +86,7 @@ fn intersect(r1: TextRange, r2: TextRange) -> Option { } proptest! { -#[test] + #[test] fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) { proptest_atom_text_edits_are_valid(text, edits) } -- cgit v1.2.3 From d9519791590170fd77aa45cc9a802539eaf16efc Mon Sep 17 00:00:00 2001 From: Bernardo Date: Fri, 14 Dec 2018 18:00:35 +0100 Subject: test translate_offset_with_edit against simple impl for single edits --- crates/ra_text_edit/src/lib.rs | 1 - crates/ra_text_edit/src/test_utils.rs | 70 +++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 33 deletions(-) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 0a407795e..89600413a 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -1,6 +1,5 @@ mod text_edit; pub mod text_utils; -#[cfg(test)] pub mod test_utils; pub use crate::text_edit::{TextEdit, TextEditBuilder}; diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs index 29301bff3..4a0ebc08e 100644 --- a/crates/ra_text_edit/src/test_utils.rs +++ b/crates/ra_text_edit/src/test_utils.rs @@ -1,4 +1,4 @@ -use proptest::{prelude::*, proptest, proptest_helper}; +use proptest::prelude::*; use text_unit::{TextUnit, TextRange}; use crate::AtomTextEdit; @@ -66,42 +66,48 @@ pub fn arb_edits(text: &str) -> BoxedStrategy> { .boxed() } -fn arb_text_with_edits() -> BoxedStrategy<(String, Vec)> { - let text = arb_text(); - text.prop_flat_map(|s| { - let edits = arb_edits(&s); - (Just(s), edits) - }) - .boxed() -} +#[cfg(test)] +mod tests { + use super::*; + use proptest::{proptest, proptest_helper}; -fn intersect(r1: TextRange, r2: TextRange) -> Option { - let start = r1.start().max(r2.start()); - let end = r1.end().min(r2.end()); - if start <= end { - Some(TextRange::from_to(start, end)) - } else { - None + fn arb_text_with_edits() -> BoxedStrategy<(String, Vec)> { + let text = arb_text(); + text.prop_flat_map(|s| { + let edits = arb_edits(&s); + (Just(s), edits) + }) + .boxed() } -} -proptest! { - #[test] - fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) { - proptest_atom_text_edits_are_valid(text, edits) + fn intersect(r1: TextRange, r2: TextRange) -> Option { + let start = r1.start().max(r2.start()); + let end = r1.end().min(r2.end()); + if start <= end { + Some(TextRange::from_to(start, end)) + } else { + None + } } -} - -fn proptest_atom_text_edits_are_valid(text: String, edits: Vec) { - // slicing doesn't panic - for e in &edits { - let _ = &text[e.delete]; + proptest! { + #[test] + fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) { + proptest_atom_text_edits_are_valid(text, edits) + } } - // ranges do not overlap - for (i1, e1) in edits.iter().skip(1).enumerate() { - for e2 in &edits[0..i1] { - if intersect(e1.delete, e2.delete).is_some() { - assert!(false, "Overlapping ranges {} {}", e1.delete, e2.delete); + + fn proptest_atom_text_edits_are_valid(text: String, edits: Vec) { + // slicing doesn't panic + for e in &edits { + let _ = &text[e.delete]; + } + // ranges do not overlap + for i in 1..edits.len() { + let e1 = &edits[i]; + for e2 in &edits[0..i] { + if intersect(e1.delete, e2.delete).is_some() { + assert!(false, "Overlapping ranges {} {}", e1.delete, e2.delete); + } } } } -- cgit v1.2.3 From d6312085a1ac97030fa768366585b9cfb6c955cd Mon Sep 17 00:00:00 2001 From: Bernardo Date: Fri, 21 Dec 2018 18:51:31 +0100 Subject: remove slower impl, add benchmarks --- crates/ra_text_edit/src/test_utils.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs index 4a0ebc08e..f150288f6 100644 --- a/crates/ra_text_edit/src/test_utils.rs +++ b/crates/ra_text_edit/src/test_utils.rs @@ -24,6 +24,10 @@ pub fn arb_offset(text: &str) -> BoxedStrategy { } pub fn arb_edits(text: &str) -> BoxedStrategy> { + arb_edits_custom(&text, 0, 7) +} + +pub fn arb_edits_custom(text: &str, min: usize, max: usize) -> BoxedStrategy> { if text.is_empty() { // only valid edits return Just(vec![]) @@ -37,9 +41,10 @@ pub fn arb_edits(text: &str) -> BoxedStrategy> { } let offsets = text_offsets(text); - let max_cuts = offsets.len().min(7); + let max_cuts = max.min(offsets.len()); + let min_cuts = min.min(offsets.len() - 1); - proptest::sample::subsequence(offsets, 0..max_cuts) + proptest::sample::subsequence(offsets, min_cuts..max_cuts) .prop_flat_map(|cuts| { let strategies: Vec<_> = cuts .chunks(2) -- cgit v1.2.3 From 6b2da4e5474ec064e44b7cf19523de1bab72ba27 Mon Sep 17 00:00:00 2001 From: Bernardo Date: Sun, 23 Dec 2018 15:49:14 +0100 Subject: use new translate_offset_with_edit for TryConvWith doc comments --- crates/ra_text_edit/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 89600413a..8acf10448 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -6,8 +6,10 @@ pub use crate::text_edit::{TextEdit, TextEditBuilder}; use text_unit::{TextRange, TextUnit}; +/// Must not overlap with other `AtomTextEdit`s #[derive(Debug, Clone)] pub struct AtomTextEdit { + /// Refers to offsets in the original text pub delete: TextRange, pub insert: String, } -- cgit v1.2.3 From 863ed19946d6f707ce09dd77bf26b26be73e097c Mon Sep 17 00:00:00 2001 From: Bernardo Date: Mon, 24 Dec 2018 17:01:25 +0100 Subject: remove benchmark and simplify tests --- crates/ra_text_edit/src/test_utils.rs | 72 +++++++++-------------------------- crates/ra_text_edit/src/text_edit.rs | 15 +++++--- 2 files changed, 28 insertions(+), 59 deletions(-) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs index f150288f6..f0b8dfde1 100644 --- a/crates/ra_text_edit/src/test_utils.rs +++ b/crates/ra_text_edit/src/test_utils.rs @@ -1,6 +1,6 @@ use proptest::prelude::*; use text_unit::{TextUnit, TextRange}; -use crate::AtomTextEdit; +use crate::{AtomTextEdit, TextEdit}; pub fn arb_text() -> proptest::string::RegexGeneratorStrategy { // generate multiple newlines @@ -23,11 +23,7 @@ pub fn arb_offset(text: &str) -> BoxedStrategy { } } -pub fn arb_edits(text: &str) -> BoxedStrategy> { - arb_edits_custom(&text, 0, 7) -} - -pub fn arb_edits_custom(text: &str, min: usize, max: usize) -> BoxedStrategy> { +pub fn arb_text_edit(text: &str) -> BoxedStrategy { if text.is_empty() { // only valid edits return Just(vec![]) @@ -37,14 +33,14 @@ pub fn arb_edits_custom(text: &str, min: usize, max: usize) -> BoxedStrategy = cuts .chunks(2) @@ -68,52 +64,22 @@ pub fn arb_edits_custom(text: &str, min: usize, max: usize) -> BoxedStrategy BoxedStrategy<(String, Vec)> { - let text = arb_text(); - text.prop_flat_map(|s| { - let edits = arb_edits(&s); - (Just(s), edits) - }) - .boxed() - } - - fn intersect(r1: TextRange, r2: TextRange) -> Option { - let start = r1.start().max(r2.start()); - let end = r1.end().min(r2.end()); - if start <= end { - Some(TextRange::from_to(start, end)) - } else { - None - } - } - proptest! { - #[test] - fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) { - proptest_atom_text_edits_are_valid(text, edits) - } - } +#[derive(Debug, Clone)] +pub struct ArbTextWithEdits { + pub text: String, + pub edits: TextEdit, +} - fn proptest_atom_text_edits_are_valid(text: String, edits: Vec) { - // slicing doesn't panic - for e in &edits { - let _ = &text[e.delete]; - } - // ranges do not overlap - for i in 1..edits.len() { - let e1 = &edits[i]; - for e2 in &edits[0..i] { - if intersect(e1.delete, e2.delete).is_some() { - assert!(false, "Overlapping ranges {} {}", e1.delete, e2.delete); - } - } - } - } +pub fn arb_text_with_edits() -> BoxedStrategy { + let text = arb_text(); + text.prop_flat_map(|s| { + let edits = arb_text_edit(&s); + (Just(s), edits) + }) + .prop_map(|(text, edits)| ArbTextWithEdits { text, edits }) + .boxed() } diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs index 392968d63..0881f3e1c 100644 --- a/crates/ra_text_edit/src/text_edit.rs +++ b/crates/ra_text_edit/src/text_edit.rs @@ -26,12 +26,7 @@ impl TextEditBuilder { self.atoms.push(AtomTextEdit::insert(offset, text)) } pub fn finish(self) -> TextEdit { - let mut atoms = self.atoms; - atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); - for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { - assert!(a1.delete.end() <= a2.delete.start()) - } - TextEdit { atoms } + TextEdit::from_atoms(self.atoms) } pub fn invalidates_offset(&self, offset: TextUnit) -> bool { self.atoms @@ -41,6 +36,14 @@ impl TextEditBuilder { } impl TextEdit { + pub(crate) fn from_atoms(mut atoms: Vec) -> TextEdit { + atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); + for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { + assert!(a1.delete.end() <= a2.delete.start()) + } + TextEdit { atoms } + } + pub fn as_atoms(&self) -> &[AtomTextEdit] { &self.atoms } -- cgit v1.2.3 From e9c186e48a77b536053c0f75ac9ea5b2fd322cfa Mon Sep 17 00:00:00 2001 From: Bernardo Date: Tue, 25 Dec 2018 20:49:18 +0100 Subject: change to `TextEdit` to avoid allocation and sort rename newline to step where applicable --- crates/ra_text_edit/src/test_utils.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'crates/ra_text_edit/src') diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs index f0b8dfde1..745f21c93 100644 --- a/crates/ra_text_edit/src/test_utils.rs +++ b/crates/ra_text_edit/src/test_utils.rs @@ -69,17 +69,17 @@ pub fn arb_text_edit(text: &str) -> BoxedStrategy { } #[derive(Debug, Clone)] -pub struct ArbTextWithEdits { +pub struct ArbTextWithEdit { pub text: String, - pub edits: TextEdit, + pub edit: TextEdit, } -pub fn arb_text_with_edits() -> BoxedStrategy { +pub fn arb_text_with_edit() -> BoxedStrategy { let text = arb_text(); text.prop_flat_map(|s| { - let edits = arb_text_edit(&s); - (Just(s), edits) + let edit = arb_text_edit(&s); + (Just(s), edit) }) - .prop_map(|(text, edits)| ArbTextWithEdits { text, edits }) + .prop_map(|(text, edit)| ArbTextWithEdit { text, edit }) .boxed() } -- cgit v1.2.3