1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 
5 use crate::{SmartString, SmartStringMode};
6 use std::{
7     cmp::Ordering,
8     fmt::Debug,
9     iter::FromIterator,
10     ops::{Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
11     panic::{catch_unwind, set_hook, take_hook, AssertUnwindSafe},
12 };
13 
14 #[cfg(not(test))]
15 use arbitrary::Arbitrary;
16 #[cfg(test)]
17 use proptest::proptest;
18 #[cfg(test)]
19 use proptest_derive::Arbitrary;
20 
assert_panic<A, F>(f: F) where F: FnOnce() -> A,21 pub fn assert_panic<A, F>(f: F)
22 where
23     F: FnOnce() -> A,
24 {
25     let old_hook = take_hook();
26     set_hook(Box::new(|_| {}));
27     let result = catch_unwind(AssertUnwindSafe(f));
28     set_hook(old_hook);
29     assert!(
30         result.is_err(),
31         "action that should have panicked didn't panic"
32     );
33 }
34 
35 #[derive(Arbitrary, Debug, Clone)]
36 pub enum Constructor {
37     New,
38     FromString(String),
39     FromStringSlice(String),
40     FromChars(Vec<char>),
41 }
42 
43 impl Constructor {
construct<Mode: SmartStringMode>(self) -> (String, SmartString<Mode>)44     pub fn construct<Mode: SmartStringMode>(self) -> (String, SmartString<Mode>) {
45         match self {
46             Self::New => (String::new(), SmartString::new()),
47             Self::FromString(string) => (string.clone(), SmartString::from(string)),
48             Self::FromStringSlice(string) => (string.clone(), SmartString::from(string.as_str())),
49             Self::FromChars(chars) => (
50                 String::from_iter(chars.clone()),
51                 SmartString::from_iter(chars),
52             ),
53         }
54     }
55 }
56 
57 #[derive(Arbitrary, Debug, Clone)]
58 pub enum TestBounds {
59     Range(usize, usize),
60     From(usize),
61     To(usize),
62     Full,
63     Inclusive(usize, usize),
64     ToInclusive(usize),
65 }
66 
67 impl TestBounds {
should_panic(&self, control: &str) -> bool68     fn should_panic(&self, control: &str) -> bool {
69         let len = control.len();
70         match self {
71             Self::Range(start, end)
72                 if start > end
73                     || start > &len
74                     || end > &len
75                     || !control.is_char_boundary(*start)
76                     || !control.is_char_boundary(*end) =>
77             {
78                 true
79             }
80             Self::From(start) if start > &len || !control.is_char_boundary(*start) => true,
81             Self::To(end) if end > &len || !control.is_char_boundary(*end) => true,
82             Self::Inclusive(start, end)
83                 if *end == usize::max_value()
84                     || *start > (end + 1)
85                     || start > &len
86                     || end > &len
87                     || !control.is_char_boundary(*start)
88                     || !control.is_char_boundary(*end + 1) =>
89             {
90                 true
91             }
92             Self::ToInclusive(end) if end > &len || !control.is_char_boundary(*end + 1) => true,
93             _ => false,
94         }
95     }
96 
assert_range<A, B>(&self, control: &A, subject: &B) where A: Index<Range<usize>>, B: Index<Range<usize>>, A: Index<RangeFrom<usize>>, B: Index<RangeFrom<usize>>, A: Index<RangeTo<usize>>, B: Index<RangeTo<usize>>, A: Index<RangeFull>, B: Index<RangeFull>, A: Index<RangeInclusive<usize>>, B: Index<RangeInclusive<usize>>, A: Index<RangeToInclusive<usize>>, B: Index<RangeToInclusive<usize>>, <A as Index<Range<usize>>>::Output: PartialEq<<B as Index<Range<usize>>>::Output> + Debug, <B as Index<Range<usize>>>::Output: Debug, <A as Index<RangeFrom<usize>>>::Output: PartialEq<<B as Index<RangeFrom<usize>>>::Output> + Debug, <B as Index<RangeFrom<usize>>>::Output: Debug, <A as Index<RangeTo<usize>>>::Output: PartialEq<<B as Index<RangeTo<usize>>>::Output> + Debug, <B as Index<RangeTo<usize>>>::Output: Debug, <A as Index<RangeFull>>::Output: PartialEq<<B as Index<RangeFull>>::Output> + Debug, <B as Index<RangeFull>>::Output: Debug, <A as Index<RangeInclusive<usize>>>::Output: PartialEq<<B as Index<RangeInclusive<usize>>>::Output> + Debug, <B as Index<RangeInclusive<usize>>>::Output: Debug, <A as Index<RangeToInclusive<usize>>>::Output: PartialEq<<B as Index<RangeToInclusive<usize>>>::Output> + Debug, <B as Index<RangeToInclusive<usize>>>::Output: Debug,97     fn assert_range<A, B>(&self, control: &A, subject: &B)
98     where
99         A: Index<Range<usize>>,
100         B: Index<Range<usize>>,
101         A: Index<RangeFrom<usize>>,
102         B: Index<RangeFrom<usize>>,
103         A: Index<RangeTo<usize>>,
104         B: Index<RangeTo<usize>>,
105         A: Index<RangeFull>,
106         B: Index<RangeFull>,
107         A: Index<RangeInclusive<usize>>,
108         B: Index<RangeInclusive<usize>>,
109         A: Index<RangeToInclusive<usize>>,
110         B: Index<RangeToInclusive<usize>>,
111         <A as Index<Range<usize>>>::Output: PartialEq<<B as Index<Range<usize>>>::Output> + Debug,
112         <B as Index<Range<usize>>>::Output: Debug,
113         <A as Index<RangeFrom<usize>>>::Output:
114             PartialEq<<B as Index<RangeFrom<usize>>>::Output> + Debug,
115         <B as Index<RangeFrom<usize>>>::Output: Debug,
116         <A as Index<RangeTo<usize>>>::Output:
117             PartialEq<<B as Index<RangeTo<usize>>>::Output> + Debug,
118         <B as Index<RangeTo<usize>>>::Output: Debug,
119         <A as Index<RangeFull>>::Output: PartialEq<<B as Index<RangeFull>>::Output> + Debug,
120         <B as Index<RangeFull>>::Output: Debug,
121         <A as Index<RangeInclusive<usize>>>::Output:
122             PartialEq<<B as Index<RangeInclusive<usize>>>::Output> + Debug,
123         <B as Index<RangeInclusive<usize>>>::Output: Debug,
124         <A as Index<RangeToInclusive<usize>>>::Output:
125             PartialEq<<B as Index<RangeToInclusive<usize>>>::Output> + Debug,
126         <B as Index<RangeToInclusive<usize>>>::Output: Debug,
127     {
128         match self {
129             Self::Range(start, end) => assert_eq!(control[*start..*end], subject[*start..*end]),
130             Self::From(start) => assert_eq!(control[*start..], subject[*start..]),
131             Self::To(end) => assert_eq!(control[..*end], subject[..*end]),
132             Self::Full => assert_eq!(control[..], subject[..]),
133             Self::Inclusive(start, end) => {
134                 assert_eq!(control[*start..=*end], subject[*start..=*end])
135             }
136             Self::ToInclusive(end) => assert_eq!(control[..=*end], subject[..=*end]),
137         }
138     }
139 }
140 
141 #[derive(Arbitrary, Debug, Clone)]
142 pub enum Action {
143     Slice(TestBounds),
144     Push(char),
145     PushStr(String),
146     Truncate(usize),
147     Pop,
148     Remove(usize),
149     Insert(usize, char),
150     InsertStr(usize, String),
151     SplitOff(usize),
152     Clear,
153     IntoString,
154     Retain(String),
155     Drain(TestBounds),
156     ReplaceRange(TestBounds, String),
157 }
158 
159 impl Action {
perform<Mode: SmartStringMode>( self, control: &mut String, subject: &mut SmartString<Mode>, )160     pub fn perform<Mode: SmartStringMode>(
161         self,
162         control: &mut String,
163         subject: &mut SmartString<Mode>,
164     ) {
165         match self {
166             Self::Slice(range) => {
167                 if range.should_panic(&control) {
168                     assert_panic(|| range.assert_range(control, subject))
169                 } else {
170                     range.assert_range(control, subject);
171                 }
172             }
173             Self::Push(ch) => {
174                 control.push(ch);
175                 subject.push(ch);
176             }
177             Self::PushStr(ref string) => {
178                 control.push_str(string);
179                 subject.push_str(string);
180             }
181             Self::Truncate(index) => {
182                 if index <= control.len() && !control.is_char_boundary(index) {
183                     assert_panic(|| control.truncate(index));
184                     assert_panic(|| subject.truncate(index));
185                 } else {
186                     control.truncate(index);
187                     subject.truncate(index);
188                 }
189             }
190             Self::Pop => {
191                 assert_eq!(control.pop(), subject.pop());
192             }
193             Self::Remove(index) => {
194                 if index >= control.len() || !control.is_char_boundary(index) {
195                     assert_panic(|| control.remove(index));
196                     assert_panic(|| subject.remove(index));
197                 } else {
198                     assert_eq!(control.remove(index), subject.remove(index));
199                 }
200             }
201             Self::Insert(index, ch) => {
202                 if index > control.len() || !control.is_char_boundary(index) {
203                     assert_panic(|| control.insert(index, ch));
204                     assert_panic(|| subject.insert(index, ch));
205                 } else {
206                     control.insert(index, ch);
207                     subject.insert(index, ch);
208                 }
209             }
210             Self::InsertStr(index, ref string) => {
211                 if index > control.len() || !control.is_char_boundary(index) {
212                     assert_panic(|| control.insert_str(index, string));
213                     assert_panic(|| subject.insert_str(index, string));
214                 } else {
215                     control.insert_str(index, string);
216                     subject.insert_str(index, string);
217                 }
218             }
219             Self::SplitOff(index) => {
220                 if !control.is_char_boundary(index) {
221                     assert_panic(|| control.split_off(index));
222                     assert_panic(|| subject.split_off(index));
223                 } else {
224                     assert_eq!(control.split_off(index), subject.split_off(index));
225                 }
226             }
227             Self::Clear => {
228                 control.clear();
229                 subject.clear();
230             }
231             Self::IntoString => {
232                 assert_eq!(control, &Into::<String>::into(subject.clone()));
233             }
234             Self::Retain(filter) => {
235                 let f = |ch| filter.contains(ch);
236                 control.retain(f);
237                 subject.retain(f);
238             }
239             Self::Drain(range) => {
240                 // FIXME: ignoring inclusive bounds at usize::max_value(), pending https://github.com/rust-lang/rust/issues/72237
241                 match range {
242                     TestBounds::Inclusive(_, end) if end == usize::max_value() => return,
243                     TestBounds::ToInclusive(end) if end == usize::max_value() => return,
244                     _ => {}
245                 }
246                 if range.should_panic(&control) {
247                     assert_panic(|| match range {
248                         TestBounds::Range(start, end) => {
249                             (control.drain(start..end), subject.drain(start..end))
250                         }
251                         TestBounds::From(start) => (control.drain(start..), subject.drain(start..)),
252                         TestBounds::To(end) => (control.drain(..end), subject.drain(..end)),
253                         TestBounds::Full => (control.drain(..), subject.drain(..)),
254                         TestBounds::Inclusive(start, end) => {
255                             (control.drain(start..=end), subject.drain(start..=end))
256                         }
257                         TestBounds::ToInclusive(end) => {
258                             (control.drain(..=end), subject.drain(..=end))
259                         }
260                     })
261                 } else {
262                     let (control_iter, subject_iter) = match range {
263                         TestBounds::Range(start, end) => {
264                             (control.drain(start..end), subject.drain(start..end))
265                         }
266                         TestBounds::From(start) => (control.drain(start..), subject.drain(start..)),
267                         TestBounds::To(end) => (control.drain(..end), subject.drain(..end)),
268                         TestBounds::Full => (control.drain(..), subject.drain(..)),
269                         TestBounds::Inclusive(start, end) => {
270                             (control.drain(start..=end), subject.drain(start..=end))
271                         }
272                         TestBounds::ToInclusive(end) => {
273                             (control.drain(..=end), subject.drain(..=end))
274                         }
275                     };
276                     let control_result: String = control_iter.collect();
277                     let subject_result: String = subject_iter.collect();
278                     assert_eq!(control_result, subject_result);
279                 }
280             }
281             Self::ReplaceRange(range, string) => {
282                 // FIXME: ignoring inclusive bounds at usize::max_value(), pending https://github.com/rust-lang/rust/issues/72237
283                 match range {
284                     TestBounds::Inclusive(_, end) if end == usize::max_value() => return,
285                     TestBounds::ToInclusive(end) if end == usize::max_value() => return,
286                     _ => {}
287                 }
288                 if range.should_panic(&control) {
289                     assert_panic(|| match range {
290                         TestBounds::Range(start, end) => {
291                             control.replace_range(start..end, &string);
292                             subject.replace_range(start..end, &string);
293                         }
294                         TestBounds::From(start) => {
295                             control.replace_range(start.., &string);
296                             subject.replace_range(start.., &string);
297                         }
298                         TestBounds::To(end) => {
299                             control.replace_range(..end, &string);
300                             subject.replace_range(..end, &string);
301                         }
302                         TestBounds::Full => {
303                             control.replace_range(.., &string);
304                             subject.replace_range(.., &string);
305                         }
306                         TestBounds::Inclusive(start, end) => {
307                             control.replace_range(start..=end, &string);
308                             subject.replace_range(start..=end, &string);
309                         }
310                         TestBounds::ToInclusive(end) => {
311                             control.replace_range(..=end, &string);
312                             subject.replace_range(..=end, &string);
313                         }
314                     })
315                 } else {
316                     match range {
317                         TestBounds::Range(start, end) => {
318                             control.replace_range(start..end, &string);
319                             subject.replace_range(start..end, &string);
320                         }
321                         TestBounds::From(start) => {
322                             control.replace_range(start.., &string);
323                             subject.replace_range(start.., &string);
324                         }
325                         TestBounds::To(end) => {
326                             control.replace_range(..end, &string);
327                             subject.replace_range(..end, &string);
328                         }
329                         TestBounds::Full => {
330                             control.replace_range(.., &string);
331                             subject.replace_range(.., &string);
332                         }
333                         TestBounds::Inclusive(start, end) => {
334                             control.replace_range(start..=end, &string);
335                             subject.replace_range(start..=end, &string);
336                         }
337                         TestBounds::ToInclusive(end) => {
338                             control.replace_range(..=end, &string);
339                             subject.replace_range(..=end, &string);
340                         }
341                     }
342                 }
343             }
344         }
345     }
346 }
347 
assert_invariants<Mode: SmartStringMode>(control: &str, subject: &SmartString<Mode>)348 fn assert_invariants<Mode: SmartStringMode>(control: &str, subject: &SmartString<Mode>) {
349     assert_eq!(control.len(), subject.len());
350     assert_eq!(control, subject.as_str());
351     if Mode::DEALLOC {
352         assert_eq!(
353             subject.is_inline(),
354             subject.len() <= Mode::MAX_INLINE,
355             "len {} should be inline (MAX_INLINE = {}) but was boxed",
356             subject.len(),
357             Mode::MAX_INLINE
358         );
359     }
360     assert_eq!(
361         control.partial_cmp("ordering test"),
362         subject.partial_cmp("ordering test")
363     );
364     let control_smart: SmartString<Mode> = control.into();
365     assert_eq!(Ordering::Equal, subject.cmp(&control_smart));
366 }
367 
test_everything<Mode: SmartStringMode>(constructor: Constructor, actions: Vec<Action>)368 pub fn test_everything<Mode: SmartStringMode>(constructor: Constructor, actions: Vec<Action>) {
369     let (mut control, mut subject): (_, SmartString<Mode>) = constructor.construct();
370     assert_invariants(&control, &subject);
371     for action in actions {
372         action.perform(&mut control, &mut subject);
373         assert_invariants(&control, &subject);
374     }
375 }
376 
test_ordering<Mode: SmartStringMode>(left: String, right: String)377 pub fn test_ordering<Mode: SmartStringMode>(left: String, right: String) {
378     let smart_left = SmartString::<Mode>::from(&left);
379     let smart_right = SmartString::<Mode>::from(&right);
380     assert_eq!(left.cmp(&right), smart_left.cmp(&smart_right));
381 }
382 
383 #[cfg(test)]
384 mod tests {
385     use super::{Action::*, Constructor::*, TestBounds::*, *};
386 
387     use crate::{Compact, LazyCompact};
388 
389     proptest! {
390         #[test]
391         fn proptest_everything_compact(constructor: Constructor, actions: Vec<Action>) {
392             test_everything::<Compact>(constructor, actions);
393         }
394 
395         #[test]
396         fn proptest_everything_lazycompact(constructor: Constructor, actions: Vec<Action>) {
397             test_everything::<LazyCompact>(constructor, actions);
398         }
399 
400         #[test]
401         fn proptest_ordering_compact(left: String, right: String) {
402             test_ordering::<Compact>(left,right)
403         }
404 
405         #[test]
406         fn proptest_ordering_lazycompact(left: String, right: String) {
407             test_ordering::<LazyCompact>(left,right)
408         }
409 
410         #[test]
411         fn proptest_eq(left: String, right: String) {
412             fn test_eq<Mode: SmartStringMode>(left: &str, right: &str) {
413                 let smart_left = SmartString::<Mode>::from(left);
414                 let smart_right = SmartString::<Mode>::from(right);
415                 assert_eq!(smart_left, left);
416                 assert_eq!(smart_left, *left);
417                 assert_eq!(smart_left, left.to_string());
418                 assert_eq!(smart_left == smart_right, left == right);
419                 assert_eq!(left, smart_left);
420                 assert_eq!(*left, smart_left);
421                 assert_eq!(left.to_string(), smart_left);
422             }
423             test_eq::<Compact>(&left, &right);
424             test_eq::<LazyCompact>(&left, &right);
425         }
426     }
427 
428     #[test]
must_panic_on_insert_outside_char_boundary()429     fn must_panic_on_insert_outside_char_boundary() {
430         test_everything::<Compact>(
431             Constructor::FromString("a0 A୦a\u{2de0}0 ��Aa".to_string()),
432             vec![
433                 Action::Push(' '),
434                 Action::Push('¡'),
435                 Action::Pop,
436                 Action::Pop,
437                 Action::Push('¡'),
438                 Action::Pop,
439                 Action::Push('��'),
440                 Action::Push('\u{e000}'),
441                 Action::Pop,
442                 Action::Insert(14, 'A'),
443             ],
444         );
445     }
446 
447     #[test]
must_panic_on_out_of_bounds_range()448     fn must_panic_on_out_of_bounds_range() {
449         test_everything::<Compact>(
450             Constructor::New,
451             vec![Action::Slice(TestBounds::Range(0, 13764126361151078400))],
452         );
453     }
454 
455     #[test]
must_not_promote_before_insert_succeeds()456     fn must_not_promote_before_insert_succeeds() {
457         test_everything::<Compact>(
458             Constructor::FromString("ኲΣ A��a ®Σ a0��  aA®A".to_string()),
459             vec![Action::Insert(21, ' ')],
460         );
461     }
462 
463     #[test]
must_panic_on_slice_outside_char_boundary()464     fn must_panic_on_slice_outside_char_boundary() {
465         test_everything::<Compact>(
466             Constructor::New,
467             vec![Action::Push('Ь'), Action::Slice(TestBounds::ToInclusive(0))],
468         )
469     }
470 
471     #[test]
dont_panic_when_inserting_a_string_at_exactly_inline_capacity()472     fn dont_panic_when_inserting_a_string_at_exactly_inline_capacity() {
473         let string: String = (0..Compact::MAX_INLINE).map(|_| '\u{0}').collect();
474         test_everything::<Compact>(Constructor::New, vec![Action::InsertStr(0, string)])
475     }
476 
477     #[test]
478     #[should_panic]
drain_bounds_integer_overflow_must_panic()479     fn drain_bounds_integer_overflow_must_panic() {
480         let mut string = SmartString::<Compact>::from("מ");
481         string.drain(..=usize::max_value());
482     }
483 
484     #[test]
shouldnt_panic_on_inclusive_range_end_one_less_than_start()485     fn shouldnt_panic_on_inclusive_range_end_one_less_than_start() {
486         test_everything::<Compact>(
487             Constructor::FromString("\'\'\'\'\'[[[[[[[[[[[-[[[[[[[[[[[[[[[[[[[[[[".to_string()),
488             vec![Action::Slice(TestBounds::Inclusive(1, 0))],
489         )
490     }
491 
492     #[test]
drain_over_inline_boundary()493     fn drain_over_inline_boundary() {
494         test_everything::<Compact>(
495             FromString((0..24).map(|_| 'x').collect()),
496             vec![Drain(Range(0, 1))],
497         )
498     }
499 
500     #[test]
drain_wrapped_shouldnt_drop_twice()501     fn drain_wrapped_shouldnt_drop_twice() {
502         test_everything::<Compact>(
503             FromString((0..25).map(|_| 'x').collect()),
504             vec![Drain(Range(0, 1))],
505         )
506     }
507 
508     #[test]
fail()509     fn fail() {
510         let value = "fo\u{0}\u{0}\u{0}\u{8}\u{0}\u{0}\u{0}\u{0}____bbbbb_____bbbbbbbbb";
511         let mut control = String::from(value);
512         let mut string = SmartString::<Compact>::from(value);
513         control.drain(..=0);
514         string.drain(..=0);
515         let control_smart: SmartString<Compact> = control.into();
516         assert_eq!(control_smart, string);
517         assert_eq!(Ordering::Equal, string.cmp(&control_smart));
518     }
519 
520     #[test]
dont_panic_on_removing_last_index_from_an_inline_string()521     fn dont_panic_on_removing_last_index_from_an_inline_string() {
522         let mut s =
523             SmartString::<Compact>::from("\u{323}\u{323}\u{323}ω\u{323}\u{323}\u{323}㌣\u{e323}㤘");
524         s.remove(20);
525     }
526 
527     #[test]
string_layout_consistency_check()528     fn string_layout_consistency_check() {
529         let mut s = String::with_capacity(5);
530         s.push_str("lol");
531         assert_eq!(3, s.len());
532         assert_eq!(5, s.capacity());
533         let ptr: *const String = &s;
534         let ptr: *const usize = ptr.cast();
535         let first_bytes = unsafe { *ptr };
536         assert_ne!(3, first_bytes);
537         assert_ne!(5, first_bytes);
538         let first_byte = unsafe { *(ptr as *const u8) };
539         #[cfg(target_endian = "little")]
540         assert_eq!(0, first_byte & 0x01);
541         #[cfg(target_endian = "big")]
542         assert_eq!(0, first_byte & 0x80);
543     }
544 }
545