// SPDX-FileCopyrightText: Copyright (C) 2024 Roland Csaszar // SPDX-License-Identifier: MPL-2.0 // // Project: token-string // File: test-string.rs // Date: 23.Nov.2024 // ============================================================================= //! Test the `TokenString` methods. #![expect(clippy::tests_outside_test_module, reason = "tests directory")] #![expect( clippy::std_instead_of_alloc, reason = "We are testing, this needs std" )] mod func { use core::borrow::Borrow as _; use core::hash::{Hash as _, Hasher as _}; use std::hash::DefaultHasher; use assert2::{check, let_assert}; use token_string::TokenString; #[test] fn empty_is_empty() { let empty = TokenString::default(); check!(empty.is_empty()); check!(empty.len() == 0); } #[test] fn is_not_empty() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); check!(res.is_empty() == false); check!(res.len() == "Hello!".len()); } #[test] fn same_prefix_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); check!(res == res); check!(res.is_small()); } #[test] fn same_small_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello World!"), "this should be OK" ); check!(res == res); check!(res.is_small()); } #[test] fn same_heap_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello funny world!"), "this should be OK" ); check!(res == res); check!(!res.is_small()); } #[test] fn clone_prefix_eq() { let_assert!( Ok(s1) = TokenString::try_from("Hello!"), "this should be OK" ); let res = s1.clone(); check!(res == s1); check!(res.is_small()); } #[test] fn clone_small_eq() { let_assert!( Ok(s1) = TokenString::try_from("Hello World!"), "this should be OK" ); let res = s1.clone(); check!(res == s1); check!(res.is_small()); } #[test] fn clone_heap_eq() { let_assert!( Ok(s1) = TokenString::try_from("Hello funny world!"), "this should be OK" ); let res = s1.clone(); check!(res == s1); check!(!res.is_small()); } #[test] fn as_ref() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let s = res.as_ref(); check!(&res == s); check!(res.is_small()); } #[test] fn borrow() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let s: &str = res.borrow(); check!(&res == s); check!(res.is_small()); } #[test] fn prefix_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello!"), "this should be OK" ); check!(res == res2); check!(res.is_small()); } #[test] fn small_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello World!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello World!"), "this should be OK" ); check!(res == res2); check!(res.is_small()); } #[test] fn heap_eq() { let_assert!( Ok(res) = TokenString::try_from("Hello funny world!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello funny world!"), "this should be OK" ); check!(res == res2); check!(!res.is_small()); } #[test] fn as_chars() { let chars: Vec = "Hello funny world!".chars().collect(); let_assert!( Ok(res) = TokenString::try_from(chars.as_slice()), "this should be OK" ); check!(res.as_chars() == chars); check!(!res.is_small()); } #[test] fn heap_string_display() { let string = "Hello funny world!".to_owned(); let_assert!( Ok(res) = TokenString::try_from(&string), "this should be OK" ); check!(res.to_string() == string); check!(format!("{res}") == string); check!(!res.is_small()); } #[test] fn prefix_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello1"), "this should be OK" ); check!(res != res2); check!(res.is_small() == res2.is_small()); } #[test] fn small_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello World!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello World1"), "this should be OK" ); check!(res != res2); check!(res.is_small() == res2.is_small()); } #[test] fn heap_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello funny world!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("Hello funny world1"), "this should be OK" ); check!(res != res2); check!(res.is_small() == res2.is_small()); } #[test] fn string_neq() { let s = "Hello funny world!".to_owned(); let_assert!( Ok(res) = TokenString::try_from("Hello funny world"), "this should be OK" ); check!(res != s); check!(res.is_small() == false); } #[test] fn prefix_str_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let res2 = "Hello"; check!(&res != res2); } #[test] fn small_str_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello World!"), "this should be OK" ); let res2 = "Hello World"; check!(&res != res2); } #[test] fn heap_str_neq() { let_assert!( Ok(res) = TokenString::try_from("Hello funny world!"), "this should be OK" ); let res2 = "Hello funny world"; check!(&res != res2); } #[test] fn debug_print_prefix() { let_assert!(Ok(res) = TokenString::try_from("ࠀ\0¡")); let a = format!("Debug: {res:?}"); check!( a == "Debug: TokenString { len: 6, prefix: [224, 160, 128, 0, \ 194, 161], small: [], string: \"ࠀ\\0¡\" }" ); } #[test] fn debug_print_small() { let_assert!(Ok(res) = TokenString::try_from("ࠀ\0A¡")); let a = format!("Debug: {res:?}"); check!( a == "Debug: TokenString { len: 7, prefix: [224, 160, 128, 0, 65, \ 194], small: [161], string: \"ࠀ\\0A¡\" }" ); } #[test] fn debug_print_heap() { let_assert!(Ok(res) = TokenString::try_from("ࠀ\0A¡ \u{b}\u{b}ࠀ\0a")); let a = format!("Debug: {res:?}"); let ptr_str = format!("{:?}", res.as_str().as_ptr()); check!( a == format!( "Debug: TokenString {{ len: 15, prefix: [224, 160, 128, 0, \ 65, 194], ptr: ManuallyDrop {{ value: StringPtr {{ ptr: \ {ptr_str} }} }}, string: \"ࠀ\\0A¡ \\u{{b}}\\u{{b}}ࠀ\\0a\" }}" ) ); } #[test] fn ord_array() { let_assert!(Ok(s1) = TokenString::try_from("Aaaaaaaa")); let_assert!(Ok(s2) = TokenString::try_from("Aaaaaaa")); let_assert!(Ok(s3) = TokenString::try_from("Aaaaaa")); let_assert!(Ok(s4) = TokenString::try_from("Aabaaa")); let_assert!(Ok(s5) = TokenString::try_from("Aab")); let res = [&s3, &s2, &s1, &s5, &s4]; let mut res2 = [&s4, &s1, &s3, &s2, &s5]; res2.sort(); check!(res2 == res); } #[test] fn slice_prefix() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let res2 = &res[0 .. 4]; check!(&res.as_str()[0 .. 4] == res2); } #[test] fn slice_small() { let_assert!( Ok(res) = TokenString::try_from("Hello, world!"), "this should be OK" ); let res2 = &res[5 .. 12]; check!(&res.as_str()[5 .. 12] == res2); } #[test] fn slice_heap() { let_assert!( Ok(res) = TokenString::try_from("Hello, funny world!"), "this should be OK" ); let res2 = &res[7 .. 12]; check!(&res.as_str()[7 .. 12] == res2); } #[test] fn get_prefix() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let_assert!(Ok(el) = res.get(4)); check!(el == b'o'); } #[test] fn get_small() { let_assert!( Ok(res) = TokenString::try_from("Hello, world!"), "this should be OK" ); let_assert!(Ok(el) = res.get(9)); check!(el == b'r'); } #[test] fn get_heap() { let_assert!( Ok(res) = TokenString::try_from("Hello, funny world!"), "this should be OK" ); let_assert!(Ok(el) = res.get(15)); check!(el == b'r'); } #[test] fn get_unchecked_prefix() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); let el = res.get_unchecked(4); check!(el == b'o'); } #[test] fn get_unchecked_small() { let_assert!( Ok(res) = TokenString::try_from("Hello, world!"), "this should be OK" ); let el = res.get_unchecked(9); check!(el == b'r'); } #[test] fn get_unchecked_heap() { let_assert!( Ok(res) = TokenString::try_from("Hello, funny world!"), "this should be OK" ); let el = res.get_unchecked(15); check!(el == b'r'); } #[test] fn trim_prefix() { let_assert!( Ok(res) = TokenString::try_from(" Hell "), "this should be OK" ); check!(&res.trim_ascii() == "Hell"); } #[test] fn trim_small() { let_assert!( Ok(res) = TokenString::try_from(" Hello, world "), "this should be OK" ); check!(&res.trim_ascii() == "Hello, world"); check!(res.is_small() == true); } #[test] fn trim_heap() { let_assert!( Ok(res) = TokenString::try_from(" Hello, funny world "), "this should be OK" ); check!(&res.trim_ascii() == "Hello, funny world"); check!(res.is_small() == false); } #[test] fn trim_start_heap() { let_assert!( Ok(res) = TokenString::try_from(" Hello, funny world "), "this should be OK" ); check!(&res.trim_ascii_start() == "Hello, funny world "); check!(res.is_small() == false); } #[test] fn trim_end_heap() { let_assert!( Ok(res) = TokenString::try_from(" Hello, funny world "), "this should be OK" ); check!(&res.trim_ascii_end() == " Hello, funny world"); check!(res.is_small() == false); } #[test] fn is_uppercase() { let_assert!( Ok(res) = TokenString::try_from("Hello!"), "this should be OK" ); check!(res.starts_ascii_uppercase() == true); check!(res.starts_ascii_lowercase() == false); } #[test] fn is_lowercase() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.starts_ascii_uppercase() == false); check!(res.starts_ascii_lowercase() == true); } #[test] fn is_ascii() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.is_ascii() == true); } #[test] fn is_not_ascii() { let_assert!( Ok(res) = TokenString::try_from("hěllo!"), "this should be OK" ); check!(res.is_ascii() == false); } #[test] fn starts_with() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); let_assert!( Ok(pref) = TokenString::try_from("hell"), "this should be OK" ); check!(res.starts_with(&pref) == true); } #[test] fn starts_with_bytes() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.starts_with_bytes(b"hell") == true); } #[test] fn starts_with_str() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.starts_with_str("hell") == true); } #[test] fn ends_with() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); let_assert!( Ok(suf) = TokenString::try_from("world!"), "this should be OK" ); check!(res.ends_with(&suf) == true); } #[test] fn ends_with_str() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); check!(res.ends_with_str("world!") == true); } #[test] fn ends_with_bytes() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); check!(res.ends_with_bytes(b"world!") == true); } #[test] fn not_starts_with_bytes() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.starts_with_bytes(b"ell") == false); } #[test] fn not_starts_with() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); let_assert!( Ok(pref) = TokenString::try_from("ell"), "this should be OK" ); check!(res.starts_with(&pref) == false); } #[test] fn not_starts_with_str() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(res.starts_with_str("ell") == false); } #[test] fn not_ends_with() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); let_assert!( Ok(suf) = TokenString::try_from("world"), "this should be OK" ); check!(res.ends_with(&suf) == false); } #[test] fn not_ends_with_str() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); check!(res.ends_with_str("world") == false); } #[test] fn not_ends_with_bytes() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); check!(res.ends_with_bytes(b"world") == false); } #[test] fn to_lower() { let_assert!( Ok(res) = TokenString::try_from("HELLO!"), "this should be OK" ); check!(&res.to_ascii_lowercase() == "hello!"); } #[test] fn to_upper_small() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); check!(&res.to_ascii_uppercase() == "HELLO!"); } #[test] fn to_lower_small() { let_assert!( Ok(res) = TokenString::try_from("HELLO, FUNNY WORLD!"), "this should be OK" ); check!(&res.to_ascii_lowercase() == "hello, funny world!"); } #[test] fn to_upper() { let_assert!( Ok(res) = TokenString::try_from("hello, funny world!"), "this should be OK" ); check!(&res.to_ascii_uppercase() == "HELLO, FUNNY WORLD!"); } #[test] fn to_iter_owned() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); let mut iter = res.into_iter(); check!(iter.next() == Some(b'h')); } #[test] fn to_iter() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); let mut iter = (&res).into_iter(); check!(iter.next() == Some(b'h')); } #[test] fn hashing() { let_assert!( Ok(res) = TokenString::try_from("hello!"), "this should be OK" ); let_assert!( Ok(res2) = TokenString::try_from("world"), "this should be OK" ); let mut hasher1 = DefaultHasher::new(); res.hash(&mut hasher1); let hash1 = hasher1.finish(); let mut hasher2 = DefaultHasher::new(); res2.hash(&mut hasher2); let hash2 = hasher2.finish(); check!(hash1 != hash2); } #[test] #[cfg(feature = "pattern")] fn strip_suffix() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); let_assert!(Some(stripped) = res.strip_suffix("world!")); check!(&stripped == "hello "); } #[test] #[cfg(feature = "pattern")] fn strip_prefix() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); let_assert!(Some(stripped) = res.strip_prefix("hello ")); check!(&stripped == "world!"); } #[test] #[cfg(feature = "pattern")] fn contains() { let_assert!( Ok(res) = TokenString::try_from("hello world!"), "this should be OK" ); check!(res.contains("o w") == true); check!(res.contains("o q") == false); } #[test] fn debug_iter() { let act = " \u{b}\u{b}ࠀa"; let_assert!(Ok(res) = TokenString::try_from(act)); check!(res == *act, "{res:?} != '{act:?}'"); let_assert!( Ok(string_from_iter) = String::from_utf8(res.into_iter().collect::>()) ); check!( string_from_iter.len() == act.len(), "lengths differ {} != {}", string_from_iter.len(), act.len() ); // prop_assert!(string_from_iter == act, "'{string_from_iter}' != // '{act}'"); check!(string_from_iter == act, "'{string_from_iter}' != '{act}'"); } } mod errors { use assert2::{check, let_assert}; use token_string::{TkStrError, TokenString}; #[test] fn too_long() { let too_big = token_string::MAX_LENGTH + 10; let_assert!( Ok(s1) = std::string::String::from_utf8(vec![b'1'; too_big]) ); let_assert!( Err(e) = TokenString::try_from(&s1), "this should yield an Error" ); check!(e == TkStrError::TooBig(too_big)); } #[test] fn invalid_utf8() { // this is `0xDC00` as 3 bytes. `ED` is already invalid. let s1: &[u8] = &[0xED, 0xB0, 0x80]; let_assert!( Err(e) = TokenString::try_from(s1), "this should yield an Error" ); let_assert!( TkStrError::UnicodeError(_) = e, "this should yield an unicode error" ); } #[test] fn out_of_bounds() { let s1 = "Hello!"; #[expect( clippy::cast_possible_truncation, reason = "does not overflow u16" )] let too_big = s1.len() as u16; let_assert!(Ok(res) = TokenString::try_from(s1)); let_assert!(Err(e) = res.get(too_big), "this must yield an error"); check!(e == TkStrError::OutOfBounds(too_big as usize)); } #[test] fn out_of_bounds_panic() { let s1 = "Hello!"; #[expect( clippy::cast_possible_truncation, reason = "does not overflow u16" )] let too_big = s1.len() as u16; let_assert!(Ok(res) = TokenString::try_from(s1)); let_assert!( Err(panics) = std::panic::catch_unwind(|| res.get_unchecked(too_big)), "this should panic with an out of bounds message" ); let_assert!(Some(msg) = panics.downcast_ref::()); let exp = format!("index {too_big} out of bounds"); check!(msg == &exp); } } mod properties { use assert2::let_assert; use proptest::prelude::*; use token_string::TokenString; proptest! { #[test] fn roundtrip(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res.len() == act.len(), "lengths differ: {} != {}", res.len(), act.len()); prop_assert!(res == act, "{res:?} != '{act}'"); } #[test] fn roundtrip_bytes(s in ".*") { let act = s.as_bytes(); let_assert!(Ok(res) = TokenString::try_from(act)); prop_assert!(res.len() == act.len(), "lengths differ: {} != {}", res.len(), act.len()); prop_assert!(res == *act, "{res:?} != '{act:?}'"); } #[test] fn roundtrip_str(s in ".*") { let act = s.as_str(); let_assert!(Ok(res) = TokenString::try_from(act)); prop_assert!(res.len() == act.len(), "lengths differ: {} != {}", res.len(), act.len()); prop_assert!(res == *act, "{res:?} != '{act:?}'"); } #[test] fn roundtrip_to_string(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res.len() == act.len(), "lengths differ: {} != {}", res.len(), act.len()); let res_ = res.to_string(); prop_assert!(res_ == *act, "{res:?} != '{act:?}'"); } #[test] fn roundtrip_iter_owned(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res == *act, "{res:?} != '{act:?}'"); let_assert!( Ok(string_from_iter) = String::from_utf8(res.into_iter().collect::>()) ); prop_assert!( string_from_iter.len() == act.len(), "lengths differ {} != {}", string_from_iter.len(), act.len() ); prop_assert!(string_from_iter == act, "'{string_from_iter}' != '{act}'"); } #[test] fn roundtrip_iter(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res == *act, "{res:?} != '{act:?}'"); let_assert!( Ok(string_from_iter) = String::from_utf8(res.iter().collect::>()) ); prop_assert!( string_from_iter.len() == act.len(), "lengths differ {} != {}", string_from_iter.len(), act.len() ); prop_assert!(string_from_iter == act, "'{string_from_iter}' != '{act}'"); } #[test] fn roundtrip_ref_iter(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res == act, "{res:?} != '{act:?}'"); let_assert!( Ok(string_from_iter) = String::from_utf8((&res).into_iter().collect::>()) ); prop_assert!( string_from_iter.len() == act.len(), "lengths differ {} != {}", string_from_iter.len(), act.len() ); prop_assert!(string_from_iter == act, "'{string_from_iter}' != '{act}'"); } #[test] fn roundtrip_char_iter(act in ".*") { let_assert!(Ok(res) = TokenString::try_from(&act)); prop_assert!(res == *act, "{res:?} != '{act:?}'"); let string_from_iter = res.chars().collect::(); prop_assert!( string_from_iter.len() == act.len(), "lengths differ {} != {}", string_from_iter.len(), act.len() ); prop_assert!(string_from_iter == act, "'{string_from_iter}' != '{act}'"); } } }