turbocow 0.3.0-beta.2

Compact, clone-on-write vectors, strings, maps and sets with inline + referenced storage — a superset of ecow.
Documentation
use turbocow::{EcoByteString, EcoString};

#[test]
fn test_new_empty() {
    let s = EcoByteString::new();
    assert!(s.is_empty());
    assert_eq!(s.len(), 0);
    assert_eq!(&*s, b"");
}

#[test]
fn test_from_bytes_inline() {
    let s = EcoByteString::from_bytes(b"hello");
    assert_eq!(s.len(), 5);
    assert_eq!(&*s, b"hello");
}

#[test]
fn test_from_bytes_spill() {
    let data = vec![0xffu8; 64];
    let s = EcoByteString::from_bytes(&data);
    assert_eq!(s.len(), 64);
    assert_eq!(&*s, &data[..]);
}

#[test]
fn test_inline_exact_limit() {
    let data = vec![0x42u8; EcoByteString::INLINE_LIMIT];
    let s = EcoByteString::inline(&data);
    assert_eq!(s.len(), EcoByteString::INLINE_LIMIT);
    assert_eq!(&*s, &data[..]);
}

#[test]
#[should_panic(expected = "exceeded inline capacity")]
fn test_inline_exceeds_limit() {
    let data = vec![0x42u8; EcoByteString::INLINE_LIMIT + 1];
    let _ = EcoByteString::inline(&data);
}

#[test]
fn test_from_static() {
    let s = EcoByteString::from_static(b"static bytes");
    assert_eq!(&*s, b"static bytes");
}

#[test]
fn test_push() {
    let mut s = EcoByteString::new();
    s.push(0x41);
    s.push(0x42);
    s.push(0x43);
    assert_eq!(&*s, b"ABC");
}

#[test]
fn test_push_spill() {
    let mut s = EcoByteString::from_bytes(&[0u8; 15]);
    assert_eq!(s.len(), 15);
    s.push(0xff);
    assert_eq!(s.len(), 16);
    assert_eq!(s[15], 0xff);
}

#[test]
fn test_extend_from_slice() {
    let mut s = EcoByteString::from_bytes(b"hello");
    s.extend_from_slice(b" world");
    assert_eq!(&*s, b"hello world");
}

#[test]
fn test_extend_from_slice_spill() {
    let mut s = EcoByteString::from_bytes(b"hello");
    s.extend_from_slice(&[0xffu8; 20]);
    assert_eq!(s.len(), 25);
    assert_eq!(&s[..5], b"hello");
    assert!(s[5..].iter().all(|&b| b == 0xff));
}

#[test]
fn test_pop() {
    let mut s = EcoByteString::from_bytes(b"abc");
    assert_eq!(s.pop(), Some(b'c'));
    assert_eq!(s.pop(), Some(b'b'));
    assert_eq!(s.pop(), Some(b'a'));
    assert_eq!(s.pop(), None);
}

#[test]
fn test_clear() {
    let mut s = EcoByteString::from_bytes(b"hello");
    s.clear();
    assert!(s.is_empty());
}

#[test]
fn test_truncate() {
    let mut s = EcoByteString::from_bytes(b"hello world");
    s.truncate(5);
    assert_eq!(&*s, b"hello");
}

#[test]
fn test_truncate_noop() {
    let mut s = EcoByteString::from_bytes(b"hello");
    s.truncate(100);
    assert_eq!(&*s, b"hello");
}

#[test]
fn test_make_mut() {
    let mut s = EcoByteString::from_bytes(b"hello");
    let slice = s.make_mut();
    slice[0] = b'H';
    assert_eq!(&*s, b"Hello");
}

#[test]
fn test_clone_on_write() {
    let s1 = EcoByteString::from_bytes(&[0xaau8; 32]);
    let mut s2 = s1.clone();
    s2.make_mut()[0] = 0xbb;
    assert_eq!(s1[0], 0xaa);
    assert_eq!(s2[0], 0xbb);
}

#[test]
fn test_eq() {
    let a = EcoByteString::from_bytes(b"hello");
    let b = EcoByteString::from_bytes(b"hello");
    let c = EcoByteString::from_bytes(b"world");
    assert_eq!(a, b);
    assert_ne!(a, c);
}

#[test]
fn test_eq_slice() {
    let s = EcoByteString::from_bytes(b"hello");
    assert_eq!(s, b"hello"[..]);
    assert_eq!(&b"hello"[..], s);
}

#[test]
fn test_eq_array() {
    let s = EcoByteString::from_bytes(b"hello");
    assert_eq!(s, *b"hello");
    assert_eq!(s, b"hello");
}

#[test]
fn test_ord() {
    let a = EcoByteString::from_bytes(b"abc");
    let b = EcoByteString::from_bytes(b"abd");
    assert!(a < b);
}

#[test]
fn test_hash() {
    use std::collections::HashSet;
    let mut set = HashSet::new();
    set.insert(EcoByteString::from_bytes(b"hello"));
    set.insert(EcoByteString::from_bytes(b"hello"));
    assert_eq!(set.len(), 1);
}

#[test]
fn test_debug() {
    let s = EcoByteString::from_bytes(b"hi");
    let debug = format!("{:?}", s);
    assert!(debug.contains("104")); // 'h' = 104
}

#[test]
fn test_default() {
    let s = EcoByteString::default();
    assert!(s.is_empty());
}

#[test]
fn test_from_slice_ref() {
    let s: EcoByteString = (&b"hello"[..]).into();
    assert_eq!(&*s, b"hello");
}

#[test]
fn test_from_array_ref() {
    let s: EcoByteString = b"hello".into();
    assert_eq!(&*s, b"hello");
}

#[test]
fn test_from_vec() {
    let v = vec![1u8, 2, 3];
    let s: EcoByteString = v.into();
    assert_eq!(&*s, &[1, 2, 3]);
}

#[test]
fn test_into_vec() {
    let s = EcoByteString::from_bytes(b"abc");
    let v: Vec<u8> = s.into();
    assert_eq!(v, vec![b'a', b'b', b'c']);
}

#[test]
fn test_from_iter_bytes() {
    let s: EcoByteString = (0u8..5).collect();
    assert_eq!(&*s, &[0, 1, 2, 3, 4]);
}

#[test]
fn test_from_iter_slices() {
    let slices: Vec<&[u8]> = vec![b"hello", b" ", b"world"];
    let s: EcoByteString = slices.into_iter().collect();
    assert_eq!(&*s, b"hello world");
}

#[test]
fn test_extend_bytes() {
    let mut s = EcoByteString::from_bytes(b"hi");
    s.extend(*b"!!");
    assert_eq!(&*s, b"hi!!");
}

#[test]
fn test_extend_slices() {
    let mut s = EcoByteString::from_bytes(b"hi");
    s.extend([&b" world"[..], &b"!"[..]]);
    assert_eq!(&*s, b"hi world!");
}

#[test]
fn test_repeat() {
    let s = EcoByteString::from_bytes(b"ab");
    let r = s.repeat(3);
    assert_eq!(&*r, b"ababab");
}

#[test]
fn test_with_capacity() {
    let mut s = EcoByteString::with_capacity(100);
    assert!(s.is_empty());
    s.extend_from_slice(&[0x42u8; 50]);
    assert_eq!(s.len(), 50);
}

#[test]
fn test_non_utf8_data() {
    // EcoByteString should handle arbitrary bytes, including invalid UTF-8.
    let data: Vec<u8> = (0u8..=255).collect();
    let s = EcoByteString::from_bytes(&data);
    assert_eq!(s.len(), 256);
    assert_eq!(&*s, &data[..]);
}

#[test]
fn test_borrow_and_asref() {
    use std::borrow::Borrow;
    let s = EcoByteString::from_bytes(b"test");
    let _: &[u8] = s.borrow();
    let _: &[u8] = s.as_ref();
}

#[test]
fn test_clone_from_static() {
    let s = EcoByteString::from_static(b"static data here!!");
    let s2 = s.clone();
    assert_eq!(&*s, &*s2);
}

#[test]
fn test_from_static_mutate() {
    let mut s = EcoByteString::from_static(b"hello world!!!!!");
    assert_eq!(s.len(), 16);
    s.push(b'!');
    assert_eq!(s.len(), 17);
    assert_eq!(&s[..16], b"hello world!!!!!");
    assert_eq!(s[16], b'!');
}

// --- Conversion tests ---

#[test]
fn test_into_eco_string_valid_utf8() {
    let bs = EcoByteString::from_bytes(b"hello");
    let s = bs.into_eco_string().unwrap();
    assert_eq!(s, "hello");
}

#[test]
fn test_into_eco_string_invalid_utf8() {
    let bs = EcoByteString::from_bytes(&[0xff, 0xfe]);
    let result = bs.into_eco_string();
    assert!(result.is_err());
    assert_eq!(&*result.unwrap_err(), &[0xff, 0xfe]);
}

#[test]
fn test_into_eco_string_lossy() {
    let bs = EcoByteString::from_bytes(b"hello \xff world");
    let s = bs.into_eco_string_lossy();
    assert!(s.contains('\u{FFFD}'));
    assert!(s.starts_with("hello "));
    assert!(s.ends_with(" world"));
}

#[test]
fn test_into_eco_string_lossy_valid() {
    let bs = EcoByteString::from_bytes(b"perfectly valid utf8");
    let s = bs.into_eco_string_lossy();
    assert_eq!(s, "perfectly valid utf8");
}

#[test]
fn test_from_eco_string() {
    let s = EcoString::from("hello world");
    let bs: EcoByteString = s.into();
    assert_eq!(&*bs, b"hello world");
}

#[test]
fn test_from_eco_string_ref() {
    let s = EcoString::from("hello");
    let bs: EcoByteString = (&s).into();
    assert_eq!(&*bs, b"hello");
    assert_eq!(s, "hello"); // original still alive
}

#[test]
fn test_roundtrip_eco_string_byte_string() {
    let original = EcoString::from("roundtrip test!");
    let bs: EcoByteString = original.into();
    let back = bs.into_eco_string().unwrap();
    assert_eq!(back, "roundtrip test!");
}

#[test]
fn test_into_eco_string_spilled() {
    // Spilled (>15 bytes) valid UTF-8
    let data = "this string is longer than fifteen bytes";
    let bs = EcoByteString::from_bytes(data.as_bytes());
    let s = bs.into_eco_string().unwrap();
    assert_eq!(s.as_str(), data);
}

#[test]
fn test_from_eco_string_spilled() {
    let s = EcoString::from("this is a long string that spills to heap");
    let bs: EcoByteString = s.into();
    assert_eq!(&*bs, b"this is a long string that spills to heap");
}