aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorBernardo <[email protected]>2018-12-12 17:51:43 +0000
committerBernardo <[email protected]>2018-12-25 18:45:21 +0000
commit3d98744c2a929c35852d20b5724ef5b6be1e3312 (patch)
tree2125060d997f327d6ebeccd12833be0cf8ffce8b /crates
parent5fb426cb9eeefa69a53d7c8c3367f7c6b714b9b8 (diff)
proptest strategies for TextUnit and AtomTextEdit
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_text_edit/Cargo.toml1
-rw-r--r--crates/ra_text_edit/src/lib.rs2
-rw-r--r--crates/ra_text_edit/src/test_utils.rs114
3 files changed, 117 insertions, 0 deletions
diff --git a/crates/ra_text_edit/Cargo.toml b/crates/ra_text_edit/Cargo.toml
index 3c4157a4e..67dfd0924 100644
--- a/crates/ra_text_edit/Cargo.toml
+++ b/crates/ra_text_edit/Cargo.toml
@@ -10,3 +10,4 @@ text_unit = "0.1.5"
10 10
11[dev-dependencies] 11[dev-dependencies]
12test_utils = { path = "../test_utils" } 12test_utils = { path = "../test_utils" }
13proptest = "0.8.7"
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 @@
1mod text_edit; 1mod text_edit;
2pub mod text_utils; 2pub mod text_utils;
3#[cfg(test)]
4pub mod test_utils;
3 5
4pub use crate::text_edit::{TextEdit, TextEditBuilder}; 6pub use crate::text_edit::{TextEdit, TextEditBuilder};
5 7
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 @@
1use proptest::{prelude::*, proptest, proptest_helper};
2use text_unit::{TextUnit, TextRange};
3use crate::AtomTextEdit;
4
5pub fn arb_text() -> proptest::string::RegexGeneratorStrategy<String> {
6 // generate multiple newlines
7 proptest::string::string_regex("(.*\n?)*").unwrap()
8}
9
10fn text_offsets(text: &str) -> Vec<TextUnit> {
11 text.char_indices()
12 .map(|(i, _)| TextUnit::from_usize(i))
13 .collect()
14}
15
16pub fn arb_offset(text: &str) -> BoxedStrategy<TextUnit> {
17 let offsets = text_offsets(text);
18 // this is necessary to avoid "Uniform::new called with `low >= high`" panic
19 if offsets.is_empty() {
20 Just(TextUnit::from(0)).boxed()
21 } else {
22 prop::sample::select(offsets).boxed()
23 }
24}
25
26pub fn arb_edits(text: &str) -> BoxedStrategy<Vec<AtomTextEdit>> {
27 let offsets = text_offsets(text);
28 let offsets_len = offsets.len();
29
30 if offsets_len == 0 {
31 return proptest::bool::ANY
32 .prop_flat_map(|b| {
33 // only valid edits
34 if b {
35 arb_text()
36 .prop_map(|text| vec![AtomTextEdit::insert(TextUnit::from(0), text)])
37 .boxed()
38 } else {
39 Just(vec![]).boxed()
40 }
41 })
42 .boxed();
43 }
44
45 proptest::sample::subsequence(offsets, 0..offsets_len)
46 .prop_flat_map(|xs| {
47 let strategies: Vec<_> = xs
48 .chunks(2)
49 .map(|chunk| match chunk {
50 &[from, to] => {
51 let range = TextRange::from_to(from, to);
52 (proptest::bool::ANY)
53 .prop_flat_map(move |b| {
54 if b {
55 Just(AtomTextEdit::delete(range)).boxed()
56 } else {
57 arb_text()
58 .prop_map(move |text| AtomTextEdit::replace(range, text))
59 .boxed()
60 }
61 })
62 .boxed()
63 }
64 &[x] => arb_text()
65 .prop_map(move |text| AtomTextEdit::insert(x, text))
66 .boxed(),
67 _ => unreachable!(),
68 })
69 .collect();
70 strategies
71 })
72 .boxed()
73}
74
75fn arb_text_with_edits() -> BoxedStrategy<(String, Vec<AtomTextEdit>)> {
76 let text = arb_text();
77 text.prop_flat_map(|s| {
78 let edits = arb_edits(&s);
79 (Just(s), edits)
80 })
81 .boxed()
82}
83
84fn intersect(r1: TextRange, r2: TextRange) -> Option<TextRange> {
85 let start = r1.start().max(r2.start());
86 let end = r1.end().min(r2.end());
87 if start <= end {
88 Some(TextRange::from_to(start, end))
89 } else {
90 None
91 }
92}
93
94proptest! {
95#[test]
96 fn atom_text_edits_are_valid((text, edits) in arb_text_with_edits()) {
97 proptest_atom_text_edits_are_valid(text, edits)
98 }
99}
100
101fn proptest_atom_text_edits_are_valid(text: String, edits: Vec<AtomTextEdit>) {
102 // slicing doesn't panic
103 for e in &edits {
104 let _ = &text[e.delete];
105 }
106 // ranges do not overlap
107 for (i1, e1) in edits.iter().skip(1).enumerate() {
108 for e2 in &edits[0..i1] {
109 if intersect(e1.delete, e2.delete).is_some() {
110 assert!(false, "Overlapping ranges {} {}", e1.delete, e2.delete);
111 }
112 }
113 }
114}