1
#![cfg(test)]
2

            
3
use std::assert_matches::assert_matches;
4

            
5
use proptest::prelude::*;
6

            
7
use crate::source_map::{BytePos, Pos, SourceFile, Span, Spanned};
8
use crate::tests::*;
9

            
10
257
proptest! {
11
257
    #[test]
12
257
    fn convert_byte_pos_to_and_from_usize(value: usize) {
13
257
        let byte_pos = <BytePos as Pos>::from_usize(value);
14
256
        assert_eq!(<BytePos as Pos>::to_usize(byte_pos), value);
15
257
    }
16
257
}
17

            
18
480
proptest! {
19
480
    #[test]
20
480
    fn add_two_byte_poses(a: usize, b: usize) {
21
480
        prop_assume!(a.checked_add(b).is_some());
22
480

            
23
480
        let p1 = BytePos::from_usize(a);
24
256
        let p2 = BytePos::from_usize(b);
25
256

            
26
256
        assert_eq!(p1 + p2, BytePos::from_usize(a + b));
27
480
    }
28
480
}
29

            
30
530
proptest! {
31
530
    #[test]
32
530
    fn add_assign_two_byte_poses(a: usize, b: usize) {
33
530
        prop_assume!(a.checked_add(b).is_some());
34
530

            
35
530
        let mut p1 = BytePos::from_usize(a);
36
256
        let p2 = BytePos::from_usize(b);
37
256

            
38
256
        p1 += p2;
39
256

            
40
256
        assert_eq!(p1, BytePos::from_usize(a + b));
41
530
    }
42
530
}
43

            
44
505
proptest! {
45
505
    #[test]
46
505
    fn span_from_usizes_should_create_a_span_of_byte_poses(start: usize, end: usize) {
47
505
        prop_assume!(start <= end);
48
505

            
49
505
        let span = Span::from_usizes(start, end);
50
256

            
51
256
        assert_eq!(
52
256
            span,
53
256
            Span {
54
256
                start: BytePos::from_usize(start),
55
256
                end: BytePos::from_usize(end),
56
256
            }
57
256
        );
58
505
    }
59
505
}
60

            
61
498
proptest! {
62
498
    #[test]
63
498
    fn span_from_usizes_should_not_allow_start_greater_than_end(start: usize, end: usize) {
64
498
        prop_assume!(start > end);
65
498

            
66
498
        let result = std::panic::catch_unwind(|| Span::from_usizes(start, end));
67
498

            
68
498
        assert_matches!(result, Err(_));
69
498
    }
70
498
}
71

            
72
5
fn valid_span() -> impl Strategy<Value = Span> {
73
5
    any::<usize>()
74
1280
        .prop_flat_map(|end| (0..end, Just(end)))
75
1536
        .prop_map(|(start, end)| Span {
76
1536
            start: start.into(),
77
1536
            end: end.into(),
78
1536
        })
79
5
}
80

            
81
256
proptest! {
82
256
    #[test]
83
256
    fn span_from_spanned(span in valid_span()) {
84
256
        struct S;
85
256

            
86
256
        let spanned = Spanned::new(S, span);
87
256

            
88
256
        assert_eq!(Span::from(spanned), span);
89
257
    }
90
257
}
91

            
92
256
proptest! {
93
256
    #[test]
94
256
    fn spanned_new_should_create_a_spanned_with_a_value_and_a_span(span in valid_span()) {
95
256
        #[derive(PartialEq, Debug)]
96
256
        struct S;
97
256

            
98
256
        let spanned = Spanned::new(S, span);
99
256

            
100
256
        assert_eq!(spanned.value, S);
101
257
        assert_eq!(spanned.span, span);
102
257
    }
103
257
}
104

            
105
1
#[test]
106
1
fn spanned_new_should_accept_a_dummy_span() {
107
1
    #[derive(PartialEq, Debug)]
108
1
    struct S;
109
1

            
110
1
    let spanned = Spanned::new(S, Span::dummy());
111
1

            
112
1
    assert_eq!(spanned.value, S);
113
1
    assert_eq!(spanned.span, Span::dummy());
114
1
}
115

            
116
1
#[test]
117
1
fn spanned_with_dummy_span_should_create_a_spanned_with_a_value_and_dummy_span() {
118
1
    #[derive(PartialEq, Debug)]
119
1
    struct S;
120
1

            
121
1
    let spanned = Spanned::with_dummy_span(S);
122
1

            
123
1
    assert_eq!(spanned.value, S);
124
1
    assert_eq!(spanned.span, Span::dummy());
125
1
}
126

            
127
256
proptest! {
128
256
    #[test]
129
256
    fn spanned_should_deref_to_its_internal_value(span in valid_span()) {
130
256
        #[derive(PartialEq, Debug)]
131
256
        struct S {
132
256
            i: i32,
133
256
        }
134
256

            
135
256
        let spanned = Spanned::new(S { i: 42 }, span);
136
256

            
137
256
        assert_eq!(*spanned, S { i: 42 });
138
257
        assert_eq!(spanned.i, 42);
139
257
    }
140
257
}
141

            
142
257
proptest! {
143
257
    #[test]
144
257
    fn usize_should_be_convertible_to_byte_pos(value: usize) {
145
257
        let bp = <usize as Into<BytePos>>::into(value);
146
256

            
147
256
        assert_eq!(bp, BytePos::from_usize(value));
148
257
    }
149
257
}
150

            
151
256
proptest! {
152
256
    #[test]
153
256
    fn get_text_snippet_should_return_empty_string_for_dummy_span(text in ".*") {
154
256
        let sf = SourceFile::new(&text);
155
256

            
156
256
        assert_eq!(sf.get_text_snippet(Span::dummy()), "");
157
257
    }
158
257
}
159

            
160
1
fn text_and_inbounds_byte_pos() -> impl Strategy<Value = (String, BytePos)> {
161
1
    source_chars()
162
256
        .prop_flat_map(|text| (0..text.len(), Just(text)))
163
256
        .prop_map(|(index, text)| (text, index.into()))
164
1
}
165

            
166
256
proptest! {
167
256
    #[test]
168
256
    fn get_text_snippet_should_return_empty_string_for_empty_span_and_non_empty_text(
169
256
        (text, byte_pos) in text_and_inbounds_byte_pos()
170
256
    ) {
171
256
        let sf = SourceFile::new(&text);
172
256

            
173
256
        let empty_span = Span {
174
256
            start: byte_pos,
175
256
            end: byte_pos
176
256
        };
177
256

            
178
256
        assert_eq!(sf.get_text_snippet(empty_span), "");
179
257
    }
180
257
}
181

            
182
256
proptest! {
183
256
    #[test]
184
256
    fn get_text_snippet_should_panic_if_span_is_non_dummy_and_text_is_empty(
185
256
        span in valid_span().prop_filter("no dummy", |&span| span != Span::dummy())
186
256
    ) {
187
256
        let sf = SourceFile::new("");
188
256
        let result = std::panic::catch_unwind(|| sf.get_text_snippet(span));
189
257

            
190
257
        assert_matches!(result, Err(_));
191
257
    }
192
257
}
193

            
194
256
proptest! {
195
256
    #[test]
196
256
    fn get_text_snippet_should_panic_on_fully_out_of_bounds_span(
197
256
        text in source_chars(),
198
256
        span in valid_span()
199
256
    ) {
200
256
        prop_assume!(span.start.to_usize() > text.len());
201
257

            
202
257
        let sf = SourceFile::new(&text);
203
256
        let result = std::panic::catch_unwind(|| sf.get_text_snippet(span));
204
257

            
205
257
        assert_matches!(result, Err(_));
206
257
    }
207
257
}
208

            
209
1
fn text_and_partially_out_of_bounds_span() -> impl Strategy<Value = (String, Span)> {
210
1
    source_chars()
211
256
        .prop_flat_map(|text| (0..text.len(), text.len().., Just(text)))
212
256
        .prop_map(|(inbounds_index, outbounds_index, text)| {
213
256
            (text, Span::from_usizes(inbounds_index, outbounds_index))
214
256
        })
215
1
}
216

            
217
256
proptest! {
218
256
    #[test]
219
256
    fn get_text_snippet_should_panic_on_partially_out_of_bounds_span(
220
256
        (text, span) in text_and_partially_out_of_bounds_span()
221
256
    ) {
222
256
        let sf = SourceFile::new(&text);
223
256
        let result = std::panic::catch_unwind(|| sf.get_text_snippet(span));
224
257

            
225
257
        assert_matches!(result, Err(_));
226
257
    }
227
257
}
228

            
229
256
proptest! {
230
256
    #[test]
231
256
    fn get_text_snippet_should_return_substring_for_the_exclusive_range_of_a_non_dummy_span(
232
256
        prefix in ".*",
233
256
        infix in ".*",
234
256
        suffix in ".*",
235
256
    ) {
236
256
        let start = prefix.len();
237
256
        let end = start + infix.len();
238
256
        let span = Span::from_usizes(start, end);
239
256

            
240
256
        let expected_snippet = infix.clone();
241
256

            
242
256
        let text = [prefix, infix, suffix].concat();
243
256
        let sf = SourceFile::new(&text);
244
256

            
245
256
        assert_eq!(sf.get_text_snippet(span), &expected_snippet);
246
257
    }
247
257
}
248

            
249
256
proptest! {
250
256
    #[test]
251
256
    fn get_text_snippet_should_return_whole_text_if_span_covers_it(
252
256
        text in ".*",
253
256
    ) {
254
256
        let sf = SourceFile::new(&text);
255
256

            
256
256
        let span = Span::from_usizes(0, text.len());
257
256

            
258
256
        assert_eq!(sf.get_text_snippet(span), &text);
259
257
    }
260
257
}
261

            
262
1
#[test]
263
1
fn get_text_snippet_should_work_with_utf8_text() {
264
1
    let sf = SourceFile::new("🗻∈🌏");
265
1

            
266
1
    assert_eq!(sf.get_text_snippet(Span::from_usizes(0, 4)), "🗻");
267
1
    assert_eq!(sf.get_text_snippet(Span::from_usizes(4, 7)), "∈");
268
1
    assert_eq!(sf.get_text_snippet(Span::from_usizes(7, 11)), "🌏");
269
1
}
270

            
271
1
#[test]
272
1
fn get_text_snippet_should_panic_if_span_is_broken_utf8() {
273
1
    let sf = SourceFile::new("\u{0800}");
274
1

            
275
1
    let span = Span::from_usizes(0, 1);
276
1
    let result = std::panic::catch_unwind(|| sf.get_text_snippet(span));
277

            
278
1
    assert_matches!(result, Err(_));
279
1
}
280

            
281
1
#[test]
282
1
fn get_text_snippet_should_panic_if_empty_span_start_is_not_a_utf_8_char_boundary() {
283
1
    let sf = SourceFile::new("\u{0800}");
284
1

            
285
1
    let span = Span::from_usizes(1, 1);
286
1
    let result = std::panic::catch_unwind(|| sf.get_text_snippet(span));
287

            
288
1
    assert_matches!(result, Err(_));
289
1
}