| #![allow(clippy::disallowed_types)] | |
| use std::sync::Arc; | |
| #[cfg(not(miri))] | |
| use proptest::{prop_assert, prop_assert_eq, proptest}; | |
| use smol_str::{SmolStr, SmolStrBuilder}; | |
| #[test] | |
| #[cfg(target_pointer_width = "64")] | |
| fn smol_str_is_smol() { | |
| assert_eq!(::std::mem::size_of::<SmolStr>(), ::std::mem::size_of::<String>(),); | |
| assert_eq!(::std::mem::size_of::<Option<SmolStr>>(), ::std::mem::size_of::<SmolStr>(),); | |
| } | |
| #[test] | |
| fn assert_traits() { | |
| fn f<T: Send + Sync + ::std::fmt::Debug + Clone>() {} | |
| f::<SmolStr>(); | |
| } | |
| #[test] | |
| fn conversions() { | |
| let s: SmolStr = "Hello, World!".into(); | |
| let s: String = s.into(); | |
| assert_eq!(s, "Hello, World!"); | |
| let s: SmolStr = Arc::<str>::from("Hello, World!").into(); | |
| let s: Arc<str> = s.into(); | |
| assert_eq!(s.as_ref(), "Hello, World!"); | |
| } | |
| #[test] | |
| fn const_fn_ctor() { | |
| const EMPTY: SmolStr = SmolStr::new_inline(""); | |
| const A: SmolStr = SmolStr::new_inline("A"); | |
| const HELLO: SmolStr = SmolStr::new_inline("HELLO"); | |
| const LONG: SmolStr = SmolStr::new_inline("ABCDEFGHIZKLMNOPQRSTUVW"); | |
| assert_eq!(EMPTY, SmolStr::from("")); | |
| assert_eq!(A, SmolStr::from("A")); | |
| assert_eq!(HELLO, SmolStr::from("HELLO")); | |
| assert_eq!(LONG, SmolStr::from("ABCDEFGHIZKLMNOPQRSTUVW")); | |
| } | |
| #[cfg(not(miri))] | |
| fn check_props(std_str: &str, smol: SmolStr) -> Result<(), proptest::test_runner::TestCaseError> { | |
| prop_assert_eq!(smol.as_str(), std_str); | |
| prop_assert_eq!(smol.len(), std_str.len()); | |
| prop_assert_eq!(smol.is_empty(), std_str.is_empty()); | |
| if smol.len() <= 23 { | |
| prop_assert!(!smol.is_heap_allocated()); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(not(miri))] | |
| proptest! { | |
| #[test] | |
| fn roundtrip(s: String) { | |
| check_props(s.as_str(), SmolStr::new(s.clone()))?; | |
| } | |
| #[test] | |
| fn roundtrip_spaces(s in r"( )*") { | |
| check_props(s.as_str(), SmolStr::new(s.clone()))?; | |
| } | |
| #[test] | |
| fn roundtrip_newlines(s in r"\n*") { | |
| check_props(s.as_str(), SmolStr::new(s.clone()))?; | |
| } | |
| #[test] | |
| fn roundtrip_ws(s in r"( |\n)*") { | |
| check_props(s.as_str(), SmolStr::new(s.clone()))?; | |
| } | |
| #[test] | |
| fn from_string_iter(slices in proptest::collection::vec(".*", 1..100)) { | |
| let string: String = slices.iter().map(|x| x.as_str()).collect(); | |
| let smol: SmolStr = slices.into_iter().collect(); | |
| check_props(string.as_str(), smol)?; | |
| } | |
| #[test] | |
| fn from_str_iter(slices in proptest::collection::vec(".*", 1..100)) { | |
| let string: String = slices.iter().map(|x| x.as_str()).collect(); | |
| let smol: SmolStr = slices.iter().collect(); | |
| check_props(string.as_str(), smol)?; | |
| } | |
| } | |
| #[cfg(feature = "serde")] | |
| mod serde_tests { | |
| use super::*; | |
| use serde::{Deserialize, Serialize}; | |
| use std::collections::HashMap; | |
| #[derive(Serialize, Deserialize)] | |
| struct SmolStrStruct { | |
| pub(crate) s: SmolStr, | |
| pub(crate) vec: Vec<SmolStr>, | |
| pub(crate) map: HashMap<SmolStr, SmolStr>, | |
| } | |
| #[test] | |
| fn test_serde() { | |
| let s = SmolStr::new("Hello, World"); | |
| let s = serde_json::to_string(&s).unwrap(); | |
| assert_eq!(s, "\"Hello, World\""); | |
| let s: SmolStr = serde_json::from_str(&s).unwrap(); | |
| assert_eq!(s, "Hello, World"); | |
| } | |
| #[test] | |
| fn test_serde_reader() { | |
| let s = SmolStr::new("Hello, World"); | |
| let s = serde_json::to_string(&s).unwrap(); | |
| assert_eq!(s, "\"Hello, World\""); | |
| let s: SmolStr = serde_json::from_reader(std::io::Cursor::new(s)).unwrap(); | |
| assert_eq!(s, "Hello, World"); | |
| } | |
| #[test] | |
| fn test_serde_struct() { | |
| let mut map = HashMap::new(); | |
| map.insert(SmolStr::new("a"), SmolStr::new("ohno")); | |
| let struct_ = SmolStrStruct { | |
| s: SmolStr::new("Hello, World"), | |
| vec: vec![SmolStr::new("Hello, World"), SmolStr::new("Hello, World")], | |
| map, | |
| }; | |
| let s = serde_json::to_string(&struct_).unwrap(); | |
| let _new_struct: SmolStrStruct = serde_json::from_str(&s).unwrap(); | |
| } | |
| #[test] | |
| fn test_serde_struct_reader() { | |
| let mut map = HashMap::new(); | |
| map.insert(SmolStr::new("a"), SmolStr::new("ohno")); | |
| let struct_ = SmolStrStruct { | |
| s: SmolStr::new("Hello, World"), | |
| vec: vec![SmolStr::new("Hello, World"), SmolStr::new("Hello, World")], | |
| map, | |
| }; | |
| let s = serde_json::to_string(&struct_).unwrap(); | |
| let _new_struct: SmolStrStruct = serde_json::from_reader(std::io::Cursor::new(s)).unwrap(); | |
| } | |
| #[test] | |
| fn test_serde_hashmap() { | |
| let mut map = HashMap::new(); | |
| map.insert(SmolStr::new("a"), SmolStr::new("ohno")); | |
| let s = serde_json::to_string(&map).unwrap(); | |
| let _s: HashMap<SmolStr, SmolStr> = serde_json::from_str(&s).unwrap(); | |
| } | |
| #[test] | |
| fn test_serde_hashmap_reader() { | |
| let mut map = HashMap::new(); | |
| map.insert(SmolStr::new("a"), SmolStr::new("ohno")); | |
| let s = serde_json::to_string(&map).unwrap(); | |
| let _s: HashMap<SmolStr, SmolStr> = | |
| serde_json::from_reader(std::io::Cursor::new(s)).unwrap(); | |
| } | |
| #[test] | |
| fn test_serde_vec() { | |
| let vec = vec![SmolStr::new(""), SmolStr::new("b")]; | |
| let s = serde_json::to_string(&vec).unwrap(); | |
| let _s: Vec<SmolStr> = serde_json::from_str(&s).unwrap(); | |
| } | |
| #[test] | |
| fn test_serde_vec_reader() { | |
| let vec = vec![SmolStr::new(""), SmolStr::new("b")]; | |
| let s = serde_json::to_string(&vec).unwrap(); | |
| let _s: Vec<SmolStr> = serde_json::from_reader(std::io::Cursor::new(s)).unwrap(); | |
| } | |
| } | |
| #[test] | |
| fn test_search_in_hashmap() { | |
| let mut m = ::std::collections::HashMap::<SmolStr, i32>::new(); | |
| m.insert("aaa".into(), 17); | |
| assert_eq!(17, *m.get("aaa").unwrap()); | |
| } | |
| #[test] | |
| fn test_from_char_iterator() { | |
| let examples = [ | |
| // Simple keyword-like strings | |
| ("if", false), | |
| ("for", false), | |
| ("impl", false), | |
| // Strings containing two-byte characters | |
| ("パーティーへ行かないか", true), | |
| ("パーティーへ行か", true), | |
| ("パーティーへ行_", false), | |
| ("和製漢語", false), | |
| ("部落格", false), | |
| ("사회과학원 어학연구소", true), | |
| // String containing diverse characters | |
| ("表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀", true), | |
| ]; | |
| for (raw, is_heap) in &examples { | |
| let s: SmolStr = raw.chars().collect(); | |
| assert_eq!(s.as_str(), *raw); | |
| assert_eq!(s.is_heap_allocated(), *is_heap); | |
| } | |
| // String which has too many characters to even consider inlining: Chars::size_hint uses | |
| // (`len` + 3) / 4. With `len` = 89, this results in 23, so `from_iter` will immediately | |
| // heap allocate | |
| let raw = "a".repeat(23 * 4 + 1); | |
| let s: SmolStr = raw.chars().collect(); | |
| assert_eq!(s.as_str(), raw); | |
| assert!(s.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn test_bad_size_hint_char_iter() { | |
| struct BadSizeHint<I>(I); | |
| impl<T, I: Iterator<Item = T>> Iterator for BadSizeHint<I> { | |
| type Item = T; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| self.0.next() | |
| } | |
| fn size_hint(&self) -> (usize, Option<usize>) { | |
| (1024, None) | |
| } | |
| } | |
| let data = "testing"; | |
| let collected: SmolStr = BadSizeHint(data.chars()).collect(); | |
| let new = SmolStr::new(data); | |
| assert!(!collected.is_heap_allocated()); | |
| assert!(!new.is_heap_allocated()); | |
| assert_eq!(new, collected); | |
| } | |
| #[test] | |
| fn test_to_smolstr() { | |
| use smol_str::ToSmolStr; | |
| for i in 0..26 { | |
| let a = &"abcdefghijklmnopqrstuvwxyz"[i..]; | |
| assert_eq!(a, a.to_smolstr()); | |
| assert_eq!(a, smol_str::format_smolstr!("{}", a)); | |
| } | |
| } | |
| #[test] | |
| fn test_builder_push_str() { | |
| //empty | |
| let builder = SmolStrBuilder::new(); | |
| assert_eq!("", builder.finish()); | |
| // inline push | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push_str("a"); | |
| builder.push_str("b"); | |
| let s = builder.finish(); | |
| assert!(!s.is_heap_allocated()); | |
| assert_eq!("ab", s); | |
| // inline max push | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push_str(&"a".repeat(23)); | |
| let s = builder.finish(); | |
| assert!(!s.is_heap_allocated()); | |
| assert_eq!("a".repeat(23), s); | |
| // heap push immediate | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push_str(&"a".repeat(24)); | |
| let s = builder.finish(); | |
| assert!(s.is_heap_allocated()); | |
| assert_eq!("a".repeat(24), s); | |
| // heap push succession | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push_str(&"a".repeat(23)); | |
| builder.push_str(&"a".repeat(23)); | |
| let s = builder.finish(); | |
| assert!(s.is_heap_allocated()); | |
| assert_eq!("a".repeat(46), s); | |
| // heap push on multibyte char | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push_str("ohnonononononononono!"); | |
| builder.push('🤯'); | |
| let s = builder.finish(); | |
| assert!(s.is_heap_allocated()); | |
| assert_eq!("ohnonononononononono!🤯", s); | |
| } | |
| #[test] | |
| fn test_builder_push() { | |
| //empty | |
| let builder = SmolStrBuilder::new(); | |
| assert_eq!("", builder.finish()); | |
| // inline push | |
| let mut builder = SmolStrBuilder::new(); | |
| builder.push('a'); | |
| builder.push('b'); | |
| let s = builder.finish(); | |
| assert!(!s.is_heap_allocated()); | |
| assert_eq!("ab", s); | |
| // inline max push | |
| let mut builder = SmolStrBuilder::new(); | |
| for _ in 0..23 { | |
| builder.push('a'); | |
| } | |
| let s = builder.finish(); | |
| assert!(!s.is_heap_allocated()); | |
| assert_eq!("a".repeat(23), s); | |
| // heap push | |
| let mut builder = SmolStrBuilder::new(); | |
| for _ in 0..24 { | |
| builder.push('a'); | |
| } | |
| let s = builder.finish(); | |
| assert!(s.is_heap_allocated()); | |
| assert_eq!("a".repeat(24), s); | |
| } | |
| #[test] | |
| fn test_from_char() { | |
| // ASCII char | |
| let s: SmolStr = 'a'.into(); | |
| assert_eq!(s, "a"); | |
| assert!(!s.is_heap_allocated()); | |
| // Multi-byte char (2 bytes) | |
| let s: SmolStr = SmolStr::from('ñ'); | |
| assert_eq!(s, "ñ"); | |
| assert!(!s.is_heap_allocated()); | |
| // 3-byte char | |
| let s: SmolStr = '€'.into(); | |
| assert_eq!(s, "€"); | |
| assert!(!s.is_heap_allocated()); | |
| // 4-byte char (emoji) | |
| let s: SmolStr = '🦀'.into(); | |
| assert_eq!(s, "🦀"); | |
| assert!(!s.is_heap_allocated()); | |
| } | |
| #[cfg(test)] | |
| mod test_str_ext { | |
| use smol_str::StrExt; | |
| #[test] | |
| fn large() { | |
| let lowercase = "aaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaAAAAaaaaaaaaaaaaaa".to_lowercase_smolstr(); | |
| assert_eq!(lowercase, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); | |
| assert!(lowercase.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn to_lowercase() { | |
| let lowercase = "aßΔC".to_lowercase_smolstr(); | |
| assert_eq!(lowercase, "aßδc"); | |
| assert!(!lowercase.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn to_uppercase() { | |
| let uppercase = "aßΔC".to_uppercase_smolstr(); | |
| assert_eq!(uppercase, "ASSΔC"); | |
| assert!(!uppercase.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn to_ascii_lowercase() { | |
| let uppercase = "aßΔC".to_ascii_lowercase_smolstr(); | |
| assert_eq!(uppercase, "aßΔc"); | |
| assert!(!uppercase.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn to_ascii_uppercase() { | |
| let uppercase = "aßΔC".to_ascii_uppercase_smolstr(); | |
| assert_eq!(uppercase, "AßΔC"); | |
| assert!(!uppercase.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn replace() { | |
| let result = "foo_bar_baz".replace_smolstr("ba", "do"); | |
| assert_eq!(result, "foo_dor_doz"); | |
| assert!(!result.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn replacen() { | |
| let result = "foo_bar_baz".replacen_smolstr("ba", "do", 1); | |
| assert_eq!(result, "foo_dor_baz"); | |
| assert!(!result.is_heap_allocated()); | |
| } | |
| #[test] | |
| fn replacen_1_ascii() { | |
| let result = "foo_bar_baz".replacen_smolstr("o", "u", 1); | |
| assert_eq!(result, "fuo_bar_baz"); | |
| assert!(!result.is_heap_allocated()); | |
| } | |
| } | |
| #[cfg(all(feature = "borsh", feature = "std"))] | |
| mod borsh_tests { | |
| use borsh::BorshDeserialize; | |
| use smol_str::{SmolStr, ToSmolStr}; | |
| use std::io::Cursor; | |
| #[test] | |
| fn borsh_serialize_stack() { | |
| let smolstr_on_stack = "aßΔCaßδc".to_smolstr(); | |
| let mut buffer = Vec::new(); | |
| borsh::BorshSerialize::serialize(&smolstr_on_stack, &mut buffer).unwrap(); | |
| let mut cursor = Cursor::new(buffer); | |
| let decoded: SmolStr = borsh::BorshDeserialize::deserialize_reader(&mut cursor).unwrap(); | |
| assert_eq!(smolstr_on_stack, decoded); | |
| } | |
| #[test] | |
| fn borsh_serialize_heap() { | |
| let smolstr_on_heap = "aßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδcaßΔCaßδc".to_smolstr(); | |
| let mut buffer = Vec::new(); | |
| borsh::BorshSerialize::serialize(&smolstr_on_heap, &mut buffer).unwrap(); | |
| let mut cursor = Cursor::new(buffer); | |
| let decoded: SmolStr = borsh::BorshDeserialize::deserialize_reader(&mut cursor).unwrap(); | |
| assert_eq!(smolstr_on_heap, decoded); | |
| } | |
| #[test] | |
| fn borsh_non_utf8_stack() { | |
| let invalid_utf8: Vec<u8> = vec![0xF0, 0x9F, 0x8F]; // Incomplete UTF-8 sequence | |
| let wrong_utf8 = SmolStr::from(unsafe { String::from_utf8_unchecked(invalid_utf8) }); | |
| let mut buffer = Vec::new(); | |
| borsh::BorshSerialize::serialize(&wrong_utf8, &mut buffer).unwrap(); | |
| let mut cursor = Cursor::new(buffer); | |
| let result = SmolStr::deserialize_reader(&mut cursor); | |
| assert!(result.is_err()); | |
| } | |
| #[test] | |
| fn borsh_non_utf8_heap() { | |
| let invalid_utf8: Vec<u8> = vec![ | |
| 0xC1, 0x8A, 0x5F, 0xE2, 0x3A, 0x9E, 0x3B, 0xAA, 0x01, 0x08, 0x6F, 0x2F, 0xC0, 0x32, | |
| 0xAB, 0xE1, 0x9A, 0x2F, 0x4A, 0x3F, 0x25, 0x0D, 0x8A, 0x2A, 0x19, 0x11, 0xF0, 0x7F, | |
| 0x0E, 0x80, | |
| ]; | |
| let wrong_utf8 = SmolStr::from(unsafe { String::from_utf8_unchecked(invalid_utf8) }); | |
| let mut buffer = Vec::new(); | |
| borsh::BorshSerialize::serialize(&wrong_utf8, &mut buffer).unwrap(); | |
| let mut cursor = Cursor::new(buffer); | |
| let result = SmolStr::deserialize_reader(&mut cursor); | |
| assert!(result.is_err()); | |
| } | |
| } |