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("ኲΣ Aa ®Σ 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