1use alloc::{
9 string::{String, ToString},
10 vec::Vec,
11};
12
13use crate::props::basic::ColorU;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[repr(C)]
37#[derive(Default)]
38#[allow(clippy::pub_underscore_fields)]
40pub struct EmptyStruct {
41 pub _reserved: u8,
44}
45
46
47impl EmptyStruct {
48 #[must_use]
50 pub const fn new() -> Self {
51 Self { _reserved: 0 }
52 }
53}
54
55impl From<()> for EmptyStruct {
56 fn from((): ()) -> Self {
57 Self::default()
58 }
59}
60
61impl From<EmptyStruct> for () {
62 fn from(_: EmptyStruct) -> Self {
63
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[repr(C)]
74#[derive(Default)]
75pub enum LayoutDebugMessageType {
76 #[default]
77 Info,
78 Warning,
79 Error,
80 BoxProps,
82 CssGetter,
83 BfcLayout,
85 IfcLayout,
87 TableLayout,
88 DisplayType,
89 PositionCalculation,
90}
91
92
93#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd)]
95#[repr(C)]
96pub struct LayoutDebugMessage {
97 pub message_type: LayoutDebugMessageType,
98 pub message: AzString,
99 pub location: AzString,
100}
101
102impl LayoutDebugMessage {
103 #[track_caller]
105 pub fn new(message_type: LayoutDebugMessageType, message: impl Into<String>) -> Self {
106 let location = core::panic::Location::caller();
107 Self {
108 message_type,
109 message: AzString::from_string(message.into()),
110 location: AzString::from_string(format!(
111 "{}:{}:{}",
112 location.file(),
113 location.line(),
114 location.column()
115 )),
116 }
117 }
118
119 #[track_caller]
121 pub fn info(message: impl Into<String>) -> Self {
122 Self::new(LayoutDebugMessageType::Info, message)
123 }
124
125 #[track_caller]
127 pub fn warning(message: impl Into<String>) -> Self {
128 Self::new(LayoutDebugMessageType::Warning, message)
129 }
130
131 #[track_caller]
133 pub fn error(message: impl Into<String>) -> Self {
134 Self::new(LayoutDebugMessageType::Error, message)
135 }
136
137 #[track_caller]
139 pub fn box_props(message: impl Into<String>) -> Self {
140 Self::new(LayoutDebugMessageType::BoxProps, message)
141 }
142
143 #[track_caller]
145 pub fn css_getter(message: impl Into<String>) -> Self {
146 Self::new(LayoutDebugMessageType::CssGetter, message)
147 }
148
149 #[track_caller]
151 pub fn bfc_layout(message: impl Into<String>) -> Self {
152 Self::new(LayoutDebugMessageType::BfcLayout, message)
153 }
154
155 #[track_caller]
157 pub fn ifc_layout(message: impl Into<String>) -> Self {
158 Self::new(LayoutDebugMessageType::IfcLayout, message)
159 }
160
161 #[track_caller]
163 pub fn table_layout(message: impl Into<String>) -> Self {
164 Self::new(LayoutDebugMessageType::TableLayout, message)
165 }
166
167 #[track_caller]
169 pub fn display_type(message: impl Into<String>) -> Self {
170 Self::new(LayoutDebugMessageType::DisplayType, message)
171 }
172}
173
174#[repr(C)]
179pub struct AzString {
180 pub vec: U8Vec,
181}
182
183impl_option!(
184 AzString,
185 OptionString,
186 copy = false,
187 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
188);
189
190static DEFAULT_STR: &str = "";
191
192impl Default for AzString {
193 fn default() -> Self {
194 DEFAULT_STR.into()
195 }
196}
197
198impl<'a> From<&'a str> for AzString {
199 fn from(s: &'a str) -> Self {
200 s.to_string().into()
201 }
202}
203
204impl AsRef<str> for AzString {
205 fn as_ref(&self) -> &str {
206 self.as_str()
207 }
208}
209
210impl core::fmt::Debug for AzString {
211 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212 self.as_str().fmt(f)
213 }
214}
215
216impl core::fmt::Display for AzString {
217 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218 self.as_str().fmt(f)
219 }
220}
221
222impl AzString {
223 #[inline]
224 #[must_use] pub const fn from_const_str(s: &'static str) -> Self {
225 Self {
226 vec: U8Vec::from_const_slice(s.as_bytes()),
227 }
228 }
229
230 #[inline]
245 #[must_use] pub unsafe fn from_c_str(ptr: *const i8) -> Self { unsafe {
246 if ptr.is_null() {
247 return Self::default();
248 }
249 let c_str = core::ffi::CStr::from_ptr(ptr as *const core::ffi::c_char);
250 let bytes = c_str.to_bytes();
251 Self::copy_from_bytes(bytes.as_ptr(), 0, bytes.len())
252 }}
253
254 #[inline]
267 #[must_use] pub fn copy_from_bytes(ptr: *const u8, start: usize, len: usize) -> Self {
268 let raw = U8Vec::copy_from_bytes(ptr, start, len);
269 if core::str::from_utf8(raw.as_ref()).is_ok() {
276 return Self { vec: raw };
277 }
278 let s = String::from_utf8_lossy(raw.as_ref()).into_owned();
279 Self::from_string(s)
280 }
281
282 #[inline] #[must_use] pub const fn from_string(s: String) -> Self {
284 Self {
285 vec: U8Vec::from_vec(s.into_bytes()),
286 }
287 }
288
289 #[inline]
290 #[must_use] pub fn as_str(&self) -> &str {
291 unsafe { core::str::from_utf8_unchecked(self.vec.as_ref()) }
292 }
293
294 #[inline]
297 #[must_use] pub fn clone_self(&self) -> Self {
298 Self {
299 vec: self.vec.clone_self(),
300 }
301 }
302
303 #[inline]
304 #[must_use] pub fn into_library_owned_string(self) -> String {
305 match self.vec.destructor {
306 U8VecDestructor::NoDestructor | U8VecDestructor::External(_) | U8VecDestructor::AlreadyDestroyed => {
307 self.as_str().to_string()
308 }
309 U8VecDestructor::DefaultRust => {
310 let m = core::mem::ManuallyDrop::new(self);
311 unsafe { String::from_raw_parts(m.vec.ptr.cast_mut(), m.vec.len, m.vec.cap) }
312 }
313 }
314 }
315
316 #[inline]
317 #[must_use] pub fn as_bytes(&self) -> &[u8] {
318 self.vec.as_ref()
319 }
320
321 #[inline]
322 #[must_use] pub fn into_bytes(self) -> U8Vec {
323 let m = core::mem::ManuallyDrop::new(self);
324 U8Vec {
325 ptr: m.vec.ptr,
326 len: m.vec.len,
327 cap: m.vec.cap,
328 destructor: m.vec.destructor,
329 }
330 }
331
332 #[inline]
334 #[must_use] pub const fn len(&self) -> usize {
335 self.vec.len
336 }
337
338 #[inline]
340 #[must_use] pub const fn is_empty(&self) -> bool {
341 self.vec.len == 0
342 }
343
344 #[inline]
350 #[must_use] pub fn to_c_str(&self) -> U8Vec {
351 let bytes = self.as_bytes();
352 let mut result = Vec::with_capacity(bytes.len() + 1);
353 result.extend_from_slice(bytes);
354 result.push(0); U8Vec::from_vec(result)
356 }
357
358 unsafe fn from_utf16_with_byte_order(
364 ptr: *const u8,
365 len: usize,
366 from_bytes: fn([u8; 2]) -> u16,
367 ) -> Self { unsafe {
368 if ptr.is_null() || len == 0 {
369 return Self::default();
370 }
371
372 if !len.is_multiple_of(2) {
374 return Self::default();
375 }
376
377 let byte_slice = core::slice::from_raw_parts(ptr, len);
378 let code_units: Vec<u16> = byte_slice
379 .chunks_exact(2)
380 .map(|chunk| from_bytes([chunk[0], chunk[1]]))
381 .collect();
382
383 String::from_utf16(&code_units).map_or_else(|_| Self::default(), Self::from_string)
384 }}
385
386 #[inline]
397 pub unsafe fn from_utf16_le(ptr: *const u8, len: usize) -> Self { unsafe {
398 Self::from_utf16_with_byte_order(ptr, len, u16::from_le_bytes)
399 }}
400
401 #[inline]
412 pub unsafe fn from_utf16_be(ptr: *const u8, len: usize) -> Self { unsafe {
413 Self::from_utf16_with_byte_order(ptr, len, u16::from_be_bytes)
414 }}
415
416 #[inline]
422 #[must_use] pub unsafe fn from_utf8_lossy(ptr: *const u8, len: usize) -> Self { unsafe {
423 if ptr.is_null() || len == 0 {
424 return Self::default();
425 }
426
427 let byte_slice = core::slice::from_raw_parts(ptr, len);
428 let s = String::from_utf8_lossy(byte_slice).into_owned();
429 Self::from_string(s)
430 }}
431
432 #[inline]
438 #[must_use] pub unsafe fn from_utf8(ptr: *const u8, len: usize) -> Self { unsafe {
439 if ptr.is_null() || len == 0 {
440 return Self::default();
441 }
442
443 let byte_slice = core::slice::from_raw_parts(ptr, len);
444 core::str::from_utf8(byte_slice)
445 .map_or_else(|_| Self::default(), |s| Self::from_string(s.to_string()))
446 }}
447}
448
449impl From<String> for AzString {
450 fn from(input: String) -> Self {
451 Self::from_string(input)
452 }
453}
454
455impl PartialOrd for AzString {
456 fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
457 self.as_str().partial_cmp(rhs.as_str())
458 }
459}
460
461impl Ord for AzString {
462 fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
463 self.as_str().cmp(rhs.as_str())
464 }
465}
466
467impl Clone for AzString {
468 fn clone(&self) -> Self {
469 self.clone_self()
470 }
471}
472
473impl PartialEq for AzString {
474 fn eq(&self, rhs: &Self) -> bool {
475 self.as_str().eq(rhs.as_str())
476 }
477}
478
479impl Eq for AzString {}
480
481impl core::hash::Hash for AzString {
482 fn hash<H>(&self, state: &mut H)
483 where
484 H: core::hash::Hasher,
485 {
486 self.as_str().hash(state);
487 }
488}
489
490impl core::ops::Deref for AzString {
491 type Target = str;
492
493 fn deref(&self) -> &str {
494 self.as_str()
495 }
496}
497
498impl_option!(
499 u8,
500 OptionU8,
501 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
502);
503
504impl_vec!(u8, U8Vec, U8VecDestructor, U8VecDestructorType, U8VecSlice, OptionU8);
505impl_vec_mut!(u8, U8Vec);
506impl_vec_debug!(u8, U8Vec);
507impl_vec_partialord!(u8, U8Vec);
508impl_vec_ord!(u8, U8Vec);
509impl_vec_clone!(u8, U8Vec, U8VecDestructor);
510impl_vec_partialeq!(u8, U8Vec);
511impl_vec_eq!(u8, U8Vec);
512impl_vec_hash!(u8, U8Vec);
513
514impl U8Vec {
515 #[inline] #[allow(clippy::not_unsafe_ptr_arg_deref)] #[must_use] pub fn copy_from_bytes(ptr: *const u8, start: usize, len: usize) -> Self {
524 if ptr.is_null() || len == 0 {
525 return Self::new();
526 }
527 debug_assert!(
528 start.checked_add(len).is_some(),
529 "U8Vec::copy_from_bytes: start + len overflows"
530 );
531 let slice = unsafe { core::slice::from_raw_parts(ptr.add(start), len) };
532 Self::from_vec(slice.to_vec())
533 }
534}
535
536impl_option!(
537 U8Vec,
538 OptionU8Vec,
539 copy = false,
540 [Debug, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
541);
542
543impl_vec!(u16, U16Vec, U16VecDestructor, U16VecDestructorType, U16VecSlice, OptionU16);
544impl_vec_debug!(u16, U16Vec);
545impl_vec_partialord!(u16, U16Vec);
546impl_vec_ord!(u16, U16Vec);
547impl_vec_clone!(u16, U16Vec, U16VecDestructor);
548impl_vec_partialeq!(u16, U16Vec);
549impl_vec_eq!(u16, U16Vec);
550impl_vec_hash!(u16, U16Vec);
551
552impl_vec!(f32, F32Vec, F32VecDestructor, F32VecDestructorType, F32VecSlice, OptionF32);
553impl_vec_debug!(f32, F32Vec);
554impl_vec_partialord!(f32, F32Vec);
555impl_vec_clone!(f32, F32Vec, F32VecDestructor);
556impl_vec_partialeq!(f32, F32Vec);
557
558impl_vec!(u32, U32Vec, U32VecDestructor, U32VecDestructorType, U32VecSlice, OptionU32);
560impl_vec_mut!(u32, U32Vec);
561impl_vec_debug!(u32, U32Vec);
562impl_vec_partialord!(u32, U32Vec);
563impl_vec_ord!(u32, U32Vec);
564impl_vec_clone!(u32, U32Vec, U32VecDestructor);
565impl_vec_partialeq!(u32, U32Vec);
566impl_vec_eq!(u32, U32Vec);
567impl_vec_hash!(u32, U32Vec);
568
569impl_vec!(AzString, StringVec, StringVecDestructor, StringVecDestructorType, StringVecSlice, OptionString);
570impl_vec_debug!(AzString, StringVec);
571impl_vec_partialord!(AzString, StringVec);
572impl_vec_ord!(AzString, StringVec);
573impl_vec_clone!(AzString, StringVec, StringVecDestructor);
574impl_vec_partialeq!(AzString, StringVec);
575impl_vec_eq!(AzString, StringVec);
576impl_vec_hash!(AzString, StringVec);
577
578impl From<Vec<String>> for StringVec {
579 fn from(v: Vec<String>) -> Self {
580 let new_v: Vec<AzString> = v.into_iter().map(Into::into).collect();
581 new_v.into()
582 }
583}
584
585impl_option!(
586 StringVec,
587 OptionStringVec,
588 copy = false,
589 [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
590);
591
592impl_option!(
593 u16,
594 OptionU16,
595 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
596);
597impl_option!(
598 u32,
599 OptionU32,
600 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
601);
602impl_option!(
603 u64,
604 OptionU64,
605 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
606);
607impl_option!(
608 usize,
609 OptionUsize,
610 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
611);
612impl_option!(
613 i16,
614 OptionI16,
615 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
616);
617impl_option!(
618 i32,
619 OptionI32,
620 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
621);
622impl_option!(bool, OptionBool, [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
623impl_option!(f32, OptionF32, [Debug, Copy, Clone, PartialEq]);
624impl_option!(f64, OptionF64, [Debug, Copy, Clone, PartialEq, PartialOrd]);
625
626impl core::hash::Hash for OptionF32 {
628 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
629 match self {
630 Self::None => 0u8.hash(state),
631 Self::Some(v) => {
632 1u8.hash(state);
633 v.to_bits().hash(state);
634 }
635 }
636 }
637}
638
639impl Eq for OptionF32 {}
640
641impl PartialOrd for OptionF32 {
644 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
645 Some(self.cmp(other))
646 }
647}
648
649impl Ord for OptionF32 {
650 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
651 match (self, other) {
652 (Self::None, Self::None) => core::cmp::Ordering::Equal,
653 (Self::None, Self::Some(_)) => core::cmp::Ordering::Less,
654 (Self::Some(_), Self::None) => core::cmp::Ordering::Greater,
655 (Self::Some(a), Self::Some(b)) => {
656 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
657 }
658 }
659 }
660}
661
662use alloc::sync::Arc;
675use core::cell::UnsafeCell;
676
677struct StringArenaInner {
681 chunks: UnsafeCell<Vec<Vec<u8>>>,
684 current_remaining: UnsafeCell<usize>,
687}
688
689unsafe impl Send for StringArenaInner {}
698unsafe impl Sync for StringArenaInner {}
699
700pub struct StringArena {
711 inner: Arc<StringArenaInner>,
712}
713
714impl core::fmt::Debug for StringArena {
715 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
717 f.debug_struct("StringArena").finish_non_exhaustive()
718 }
719}
720
721impl StringArena {
722 pub const CHUNK_SIZE: usize = 64 * 1024;
726
727 #[must_use] pub fn new() -> Self {
728 Self {
729 inner: Arc::new(StringArenaInner {
730 chunks: UnsafeCell::new(Vec::new()),
731 current_remaining: UnsafeCell::new(0),
732 }),
733 }
734 }
735
736 #[must_use] pub fn metrics(&self) -> (usize, usize) {
738 unsafe {
741 let chunks = &*self.inner.chunks.get();
742 let total: usize = chunks.iter().map(Vec::len).sum();
743 (chunks.len(), total)
744 }
745 }
746
747 pub fn intern(&mut self, s: &str) -> AzString {
758 let bytes = s.as_bytes();
759 let len = bytes.len();
760
761 let ptr: *const u8 = if len == 0 {
762 core::ptr::NonNull::<u8>::dangling().as_ptr()
765 } else {
766 unsafe {
768 let chunks: &mut Vec<Vec<u8>> = &mut *self.inner.chunks.get();
769 let remaining: &mut usize = &mut *self.inner.current_remaining.get();
770
771 if len > Self::CHUNK_SIZE / 2 {
774 let mut v = Vec::with_capacity(len);
775 v.extend_from_slice(bytes);
776 let p = v.as_ptr();
777 chunks.push(v);
778 *remaining = 0;
784 p
785 } else {
786 if *remaining < len {
787 chunks.push(Vec::with_capacity(Self::CHUNK_SIZE));
788 *remaining = Self::CHUNK_SIZE;
789 }
790 let chunk = chunks.last_mut().unwrap();
793 let offset = chunk.len();
794 chunk.extend_from_slice(bytes);
795 *remaining -= len;
796 chunk.as_ptr().add(offset)
797 }
798 }
799 };
800
801 let arc_raw = Arc::into_raw(Arc::clone(&self.inner));
804
805 AzString {
806 vec: U8Vec {
807 ptr,
808 len,
809 cap: arc_raw as usize,
814 destructor: U8VecDestructor::External(arena_string_destructor),
815 },
816 }
817 }
818}
819
820impl Default for StringArena {
821 fn default() -> Self {
822 Self::new()
823 }
824}
825
826extern "C" fn arena_string_destructor(vec: *mut U8Vec) {
830 unsafe {
833 let v = &mut *vec;
834 let arc_raw = v.cap as *const StringArenaInner;
835 if !arc_raw.is_null() {
836 drop(Arc::from_raw(arc_raw));
837 v.cap = 0;
840 }
841 }
842}
843
844#[cfg(test)]
845mod string_arena_tests {
846 use super::*;
847
848 #[test]
849 fn intern_round_trip() {
850 let mut arena = StringArena::new();
851 let a = arena.intern("hello");
852 let b = arena.intern("world");
853 let c = arena.intern("");
854 assert_eq!(a.as_str(), "hello");
855 assert_eq!(b.as_str(), "world");
856 assert_eq!(c.as_str(), "");
857 }
858
859 #[test]
860 fn strings_outlive_arena_handle() {
861 let a = {
862 let mut arena = StringArena::new();
863 arena.intern("survives drop of arena handle")
864 };
865 assert_eq!(a.as_str(), "survives drop of arena handle");
866 }
867
868 #[test]
869 fn oversized_string_gets_dedicated_chunk() {
870 let mut arena = StringArena::new();
871 let big = "x".repeat(StringArena::CHUNK_SIZE);
872 let s = arena.intern(&big);
873 assert_eq!(s.len(), big.len());
874 assert_eq!(s.as_str(), big.as_str());
875 }
876
877 #[test]
878 fn many_small_strings_share_chunk() {
879 let mut arena = StringArena::new();
880 let mut strings = Vec::new();
881 for i in 0..100 {
882 strings.push(arena.intern(&format!("s{i}")));
883 }
884 let (chunks, _bytes) = arena.metrics();
885 assert!(chunks <= 2, "expected ≤2 chunks for 100 small strings, got {chunks}");
886 for (i, s) in strings.iter().enumerate() {
887 assert_eq!(s.as_str(), format!("s{i}"));
888 }
889 }
890
891 #[test]
892 fn clone_deep_copies_and_is_independent() {
893 let clone = {
896 let mut arena = StringArena::new();
897
898 arena.intern("deep-copy test")
899 };
900 assert_eq!(clone.as_str(), "deep-copy test");
901 }
902}
903
904#[cfg(test)]
905#[allow(clippy::all, clippy::pedantic, clippy::nursery)]
906mod autotest_generated {
907 use super::*;
908
909 struct Fnv(u64);
916
917 impl core::hash::Hasher for Fnv {
918 fn finish(&self) -> u64 {
919 self.0
920 }
921 fn write(&mut self, bytes: &[u8]) {
922 for b in bytes {
923 self.0 ^= u64::from(*b);
924 self.0 = self.0.wrapping_mul(0x0100_0000_01b3);
925 }
926 }
927 }
928
929 fn hash_of<T: core::hash::Hash>(t: &T) -> u64 {
930 use core::hash::{Hash, Hasher};
931 let mut h = Fnv(0xcbf2_9ce4_8422_2325);
932 Hash::hash(t, &mut h);
933 h.finish()
934 }
935
936 fn utf16_bytes(s: &str, little_endian: bool) -> Vec<u8> {
938 s.encode_utf16()
939 .flat_map(|u| {
940 let b = if little_endian {
941 u.to_le_bytes()
942 } else {
943 u.to_be_bytes()
944 };
945 [b[0], b[1]]
946 })
947 .collect()
948 }
949
950 #[test]
955 fn empty_struct_new_invariants() {
956 let e = EmptyStruct::new();
957 assert_eq!(e._reserved, 0, "_reserved must always be initialized to 0");
958 assert_eq!(e, EmptyStruct::default(), "new() must equal default()");
959 }
960
961 #[test]
962 fn empty_struct_is_ffi_safe_non_zero_size() {
963 assert_eq!(size_of::<EmptyStruct>(), 1);
965 assert_eq!(align_of::<EmptyStruct>(), 1);
966 }
967
968 #[test]
969 fn empty_struct_unit_conversions_round_trip() {
970 let from_unit = EmptyStruct::from(());
971 assert_eq!(from_unit, EmptyStruct::new());
972 let back: () = EmptyStruct::new().into();
973 assert_eq!(back, ());
974 }
975
976 #[test]
977 fn empty_struct_total_order_is_trivial() {
978 let a = EmptyStruct::new();
980 let b = EmptyStruct::default();
981 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
982 assert_eq!(hash_of(&a), hash_of(&b));
983 }
984
985 #[test]
990 fn debug_message_new_records_fields_and_caller_location() {
991 let m = LayoutDebugMessage::new(LayoutDebugMessageType::Warning, "disk on fire");
992 assert_eq!(m.message_type, LayoutDebugMessageType::Warning);
993 assert_eq!(m.message.as_str(), "disk on fire");
994 assert!(
995 m.location.as_str().contains("corety.rs"),
996 "#[track_caller] must record THIS file, got {:?}",
997 m.location.as_str()
998 );
999
1000 let parts: Vec<&str> = m.location.as_str().rsplitn(3, ':').collect();
1002 assert_eq!(parts.len(), 3, "location must be file:line:column");
1003 assert!(parts[0].parse::<u32>().is_ok(), "column must parse as u32");
1004 assert!(parts[1].parse::<u32>().is_ok(), "line must parse as u32");
1005 }
1006
1007 #[test]
1008 fn debug_message_track_caller_propagates_through_helpers() {
1009 let a = LayoutDebugMessage::info("a");
1012 let b = LayoutDebugMessage::info("b");
1013 assert_ne!(
1014 a.location.as_str(),
1015 b.location.as_str(),
1016 "two call sites on different lines must record different locations"
1017 );
1018 assert!(a.location.as_str().contains("corety.rs"));
1019 }
1020
1021 #[test]
1022 fn debug_message_helpers_set_the_right_type() {
1023 assert_eq!(
1024 LayoutDebugMessage::info("x").message_type,
1025 LayoutDebugMessageType::Info
1026 );
1027 assert_eq!(
1028 LayoutDebugMessage::warning("x").message_type,
1029 LayoutDebugMessageType::Warning
1030 );
1031 assert_eq!(
1032 LayoutDebugMessage::error("x").message_type,
1033 LayoutDebugMessageType::Error
1034 );
1035 assert_eq!(
1036 LayoutDebugMessage::box_props("x").message_type,
1037 LayoutDebugMessageType::BoxProps
1038 );
1039 assert_eq!(
1040 LayoutDebugMessage::css_getter("x").message_type,
1041 LayoutDebugMessageType::CssGetter
1042 );
1043 assert_eq!(
1044 LayoutDebugMessage::bfc_layout("x").message_type,
1045 LayoutDebugMessageType::BfcLayout
1046 );
1047 assert_eq!(
1048 LayoutDebugMessage::ifc_layout("x").message_type,
1049 LayoutDebugMessageType::IfcLayout
1050 );
1051 assert_eq!(
1052 LayoutDebugMessage::table_layout("x").message_type,
1053 LayoutDebugMessageType::TableLayout
1054 );
1055 assert_eq!(
1056 LayoutDebugMessage::display_type("x").message_type,
1057 LayoutDebugMessageType::DisplayType
1058 );
1059 }
1060
1061 #[test]
1062 fn debug_message_helpers_preserve_the_message_verbatim() {
1063 for m in [
1066 LayoutDebugMessage::info(""),
1067 LayoutDebugMessage::warning(""),
1068 LayoutDebugMessage::error(""),
1069 LayoutDebugMessage::box_props(""),
1070 LayoutDebugMessage::css_getter(""),
1071 LayoutDebugMessage::bfc_layout(""),
1072 LayoutDebugMessage::ifc_layout(""),
1073 LayoutDebugMessage::table_layout(""),
1074 LayoutDebugMessage::display_type(""),
1075 ] {
1076 assert!(m.message.is_empty());
1077 assert!(!m.location.is_empty(), "location is always filled in");
1078 }
1079
1080 let weird = "ünïcødé \u{1F600}\n\t\"quoted\" \u{0}nul";
1081 assert_eq!(LayoutDebugMessage::error(weird).message.as_str(), weird);
1082 }
1083
1084 #[test]
1085 fn debug_message_handles_huge_message_without_panicking() {
1086 let huge = "m".repeat(1_000_000);
1087 let m = LayoutDebugMessage::new(LayoutDebugMessageType::PositionCalculation, huge.clone());
1088 assert_eq!(m.message.len(), 1_000_000);
1089 assert_eq!(m.message.as_str(), huge.as_str());
1090 assert_eq!(
1091 m.message_type,
1092 LayoutDebugMessageType::PositionCalculation,
1093 "the variant with no helper must still be constructible via new()"
1094 );
1095 }
1096
1097 #[test]
1098 fn debug_message_default_is_empty_info() {
1099 let m = LayoutDebugMessage::default();
1100 assert_eq!(m.message_type, LayoutDebugMessageType::Info);
1101 assert!(m.message.is_empty());
1102 assert!(m.location.is_empty(), "default() does not track a caller");
1103 assert_eq!(LayoutDebugMessageType::default(), LayoutDebugMessageType::Info);
1104 }
1105
1106 #[test]
1107 fn debug_message_accepts_string_and_str_via_into() {
1108 let from_str = LayoutDebugMessage::info("borrowed");
1110 let from_string = LayoutDebugMessage::info(String::from("owned"));
1111 assert_eq!(from_str.message.as_str(), "borrowed");
1112 assert_eq!(from_string.message.as_str(), "owned");
1113 }
1114
1115 #[test]
1116 fn debug_message_clone_is_a_deep_equal_copy() {
1117 let m = LayoutDebugMessage::error("clone me \u{1F600}");
1118 let c = m.clone();
1119 assert_eq!(c, m);
1120 assert_ne!(
1121 c.message.as_bytes().as_ptr(),
1122 m.message.as_bytes().as_ptr(),
1123 "clone must deep-copy the library-owned message bytes"
1124 );
1125 }
1126
1127 #[test]
1132 fn azstring_default_is_empty_and_readable() {
1133 let s = AzString::default();
1134 assert_eq!(s.as_str(), "");
1135 assert_eq!(s.len(), 0);
1136 assert!(s.is_empty());
1137 assert_eq!(s.as_bytes(), b"");
1138 }
1139
1140 #[test]
1141 fn azstring_from_const_str_borrows_the_static_and_never_frees_it() {
1142 const TEXT: &str = "static text";
1145 let s = AzString::from_const_str(TEXT);
1146 assert_eq!(s.as_str(), TEXT);
1147 assert_eq!(s.len(), 11);
1148 assert!(
1149 matches!(s.vec.destructor, U8VecDestructor::NoDestructor),
1150 "a &'static str must not get a freeing destructor"
1151 );
1152 assert_eq!(
1153 s.vec.ptr,
1154 TEXT.as_bytes().as_ptr(),
1155 "from_const_str must alias the static, not copy it"
1156 );
1157 }
1158
1159 #[test]
1160 fn azstring_from_const_str_empty_and_unicode() {
1161 let empty = AzString::from_const_str("");
1162 assert!(empty.is_empty());
1163 assert_eq!(empty.as_str(), "");
1164 assert_eq!(empty.len(), 0);
1165
1166 let uni = AzString::from_const_str("héllo \u{1F600}");
1167 assert_eq!(uni.as_str(), "héllo \u{1F600}");
1168 assert_eq!(uni.len(), "héllo \u{1F600}".len());
1170 assert_ne!(
1171 uni.len(),
1172 uni.as_str().chars().count(),
1173 "len() must be a byte length, not a char count"
1174 );
1175 }
1176
1177 #[test]
1178 fn azstring_from_string_round_trips_edge_values() {
1179 for input in [
1180 String::new(),
1181 String::from(" "),
1182 String::from("\t\n\r"),
1183 String::from("0"),
1184 String::from("-0"),
1185 String::from("9223372036854775807"), String::from("NaN"),
1187 String::from("inf"),
1188 String::from(" valid "),
1189 String::from("valid;garbage"),
1190 String::from("\u{1F600}\u{0301}\u{0}"), "{".repeat(10_000), ] {
1193 let s = AzString::from_string(input.clone());
1194 assert_eq!(s.as_str(), input.as_str(), "from_string must be verbatim");
1195 assert_eq!(s.len(), input.len());
1196 assert_eq!(s.is_empty(), input.is_empty());
1197 assert_eq!(s.into_library_owned_string(), input);
1199 }
1200 }
1201
1202 #[test]
1203 fn azstring_from_string_handles_a_megabyte() {
1204 let huge = "x".repeat(1_000_000);
1205 let s = AzString::from_string(huge.clone());
1206 assert_eq!(s.len(), 1_000_000);
1207 assert_eq!(s.as_str().len(), huge.len());
1208 assert!(s.as_str().bytes().all(|b| b == b'x'));
1209 }
1210
1211 #[test]
1212 fn azstring_from_string_preserves_the_original_capacity() {
1213 let mut owned = String::with_capacity(4096);
1216 owned.push_str("hi");
1217 let s = AzString::from_string(owned);
1218 assert!(matches!(s.vec.destructor, U8VecDestructor::DefaultRust));
1219 let back = s.into_library_owned_string();
1220 assert_eq!(back, "hi");
1221 assert!(
1222 back.capacity() >= 4096,
1223 "capacity must survive the AzString round-trip, got {}",
1224 back.capacity()
1225 );
1226 }
1227
1228 #[test]
1233 fn azstring_copy_from_bytes_zero_len_is_empty() {
1234 let buf = b"hello";
1235 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, 0);
1236 assert!(s.is_empty());
1237 assert_eq!(s.as_str(), "");
1238 }
1239
1240 #[test]
1241 fn azstring_copy_from_bytes_null_ptr_is_empty() {
1242 let s = AzString::copy_from_bytes(core::ptr::null(), 0, 16);
1243 assert!(s.is_empty());
1244 assert_eq!(s.as_str(), "");
1245 }
1246
1247 #[test]
1248 fn azstring_copy_from_bytes_honours_the_start_offset() {
1249 let buf = b"0123456789";
1250 let s = AzString::copy_from_bytes(buf.as_ptr(), 3, 4);
1251 assert_eq!(s.as_str(), "3456");
1252 assert_eq!(s.len(), 4);
1253 }
1254
1255 #[test]
1256 fn azstring_copy_from_bytes_start_at_end_with_zero_len_is_empty() {
1257 let buf = b"abc";
1260 let s = AzString::copy_from_bytes(buf.as_ptr(), buf.len(), 0);
1261 assert!(s.is_empty());
1262 }
1263
1264 #[test]
1265 fn azstring_copy_from_bytes_zero_len_wins_over_start_overflow() {
1266 let buf = b"abc";
1269 let s = AzString::copy_from_bytes(buf.as_ptr(), usize::MAX, 0);
1270 assert!(s.is_empty());
1271 }
1272
1273 #[test]
1274 fn azstring_copy_from_bytes_null_wins_over_max_len() {
1275 let s = AzString::copy_from_bytes(core::ptr::null(), usize::MAX, usize::MAX);
1277 assert!(s.is_empty());
1278 assert_eq!(s.as_str(), "");
1279 }
1280
1281 #[test]
1282 fn azstring_copy_from_bytes_replaces_invalid_utf8_lossily() {
1283 let buf = "héllo".as_bytes();
1286 assert_eq!(buf[1], 0xC3);
1287 assert_eq!(buf[2], 0xA9);
1288 let s = AzString::copy_from_bytes(buf.as_ptr(), 2, 2);
1289 assert_eq!(s.as_str(), "\u{FFFD}l");
1290 assert!(core::str::from_utf8(s.as_bytes()).is_ok());
1292 }
1293
1294 #[test]
1295 fn azstring_copy_from_bytes_keeps_valid_utf8_byte_for_byte() {
1296 let buf = "héllo \u{1F600}".as_bytes();
1297 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1298 assert_eq!(s.as_str(), "héllo \u{1F600}");
1299 assert_eq!(s.as_bytes(), buf);
1300 }
1301
1302 #[test]
1303 fn azstring_copy_from_bytes_preserves_interior_nul() {
1304 let buf = b"a\0b";
1305 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, 3);
1306 assert_eq!(s.len(), 3, "an interior NUL is data, not a terminator");
1307 assert_eq!(s.as_bytes(), b"a\0b");
1308 }
1309
1310 #[test]
1315 fn u8vec_copy_from_bytes_zero_len_is_empty() {
1316 let buf = b"hello";
1317 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 0, 0);
1318 assert!(v.is_empty());
1319 assert_eq!(v.as_ref(), b"");
1320 }
1321
1322 #[test]
1323 fn u8vec_copy_from_bytes_null_ptr_is_empty() {
1324 let v = U8Vec::copy_from_bytes(core::ptr::null(), 0, 8);
1325 assert!(v.is_empty());
1326 assert_eq!(v.len(), 0);
1327 }
1328
1329 #[test]
1330 fn u8vec_copy_from_bytes_null_wins_over_max_start_and_len() {
1331 let v = U8Vec::copy_from_bytes(core::ptr::null(), usize::MAX, usize::MAX);
1333 assert!(v.is_empty());
1334 }
1335
1336 #[test]
1337 fn u8vec_copy_from_bytes_zero_len_wins_over_start_overflow() {
1338 let buf = b"abc";
1340 let v = U8Vec::copy_from_bytes(buf.as_ptr(), usize::MAX, 0);
1341 assert!(v.is_empty());
1342 }
1343
1344 #[test]
1345 fn u8vec_copy_from_bytes_copies_the_requested_window() {
1346 let buf: Vec<u8> = (0u8..=255).collect();
1347 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 250, 6);
1348 assert_eq!(v.as_ref(), &[250, 251, 252, 253, 254, 255]);
1349 assert_eq!(v.len(), 6);
1350 }
1351
1352 #[test]
1353 fn u8vec_copy_from_bytes_owns_its_copy() {
1354 let v = {
1356 let buf = vec![1u8, 2, 3, 4];
1357 U8Vec::copy_from_bytes(buf.as_ptr(), 1, 2)
1358 };
1359 assert_eq!(v.as_ref(), &[2, 3]);
1360 assert!(matches!(v.destructor, U8VecDestructor::DefaultRust));
1361 }
1362
1363 #[test]
1364 fn u8vec_copy_from_bytes_accepts_all_byte_values() {
1365 let buf: Vec<u8> = (0u8..=255).collect();
1368 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1369 assert_eq!(v.as_ref(), buf.as_slice());
1370 }
1371
1372 #[test]
1377 fn azstring_from_c_str_null_is_empty() {
1378 let s = unsafe { AzString::from_c_str(core::ptr::null()) };
1379 assert!(s.is_empty());
1380 assert_eq!(s.as_str(), "");
1381 }
1382
1383 #[test]
1384 fn azstring_from_c_str_reads_up_to_the_terminator() {
1385 let c = b"hello\0trailing garbage\0";
1386 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1387 assert_eq!(s.as_str(), "hello");
1388 assert_eq!(s.len(), 5, "the NUL terminator is not part of the string");
1389 }
1390
1391 #[test]
1392 fn azstring_from_c_str_empty_c_string_is_empty() {
1393 let c = b"\0";
1394 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1395 assert!(s.is_empty());
1396 }
1397
1398 #[test]
1399 fn azstring_from_c_str_replaces_non_utf8_bytes() {
1400 let c = b"caf\xE9\0";
1403 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1404 assert_eq!(s.as_str(), "caf\u{FFFD}");
1405 assert!(core::str::from_utf8(s.as_bytes()).is_ok());
1406 }
1407
1408 #[test]
1409 fn azstring_from_c_str_handles_a_long_c_string() {
1410 let mut c = "z".repeat(100_000).into_bytes();
1411 c.push(0);
1412 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1413 assert_eq!(s.len(), 100_000);
1414 }
1415
1416 #[test]
1421 fn azstring_to_c_str_appends_exactly_one_nul() {
1422 let s = AzString::from_const_str("abc");
1423 let c = s.to_c_str();
1424 assert_eq!(c.as_ref(), b"abc\0");
1425 assert_eq!(c.len(), s.len() + 1);
1426 }
1427
1428 #[test]
1429 fn azstring_to_c_str_of_empty_is_just_the_terminator() {
1430 let c = AzString::default().to_c_str();
1431 assert_eq!(c.as_ref(), b"\0");
1432 assert_eq!(c.len(), 1);
1433 }
1434
1435 #[test]
1436 fn azstring_c_str_round_trip() {
1437 for original in ["", "abc", "héllo \u{1F600}", " spaced "] {
1438 let s = AzString::from_const_str(original);
1439 let c = s.to_c_str();
1440 let back = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1441 assert_eq!(back.as_str(), original, "C round-trip must be lossless");
1442 assert_eq!(back, s);
1443 }
1444 }
1445
1446 #[test]
1447 fn azstring_c_str_round_trip_truncates_at_an_interior_nul() {
1448 let s = AzString::from_string(String::from("a\0b"));
1452 let c = s.to_c_str();
1453 assert_eq!(c.as_ref(), b"a\0b\0", "to_c_str keeps the interior NUL");
1454 let back = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1455 assert_eq!(back.as_str(), "a", "from_c_str stops at the first NUL");
1456 }
1457
1458 #[test]
1459 fn azstring_to_c_str_is_an_independent_allocation() {
1460 let s = AzString::from_const_str("shared?");
1461 let c = s.to_c_str();
1462 assert!(matches!(c.destructor, U8VecDestructor::DefaultRust));
1463 assert_ne!(
1464 c.as_ptr(),
1465 s.as_bytes().as_ptr(),
1466 "to_c_str must copy, not alias the source"
1467 );
1468 assert_eq!(s.as_str(), "shared?", "source must be untouched");
1469 }
1470
1471 #[test]
1476 fn azstring_from_utf8_null_or_zero_len_is_empty() {
1477 let buf = b"abc";
1478 assert!(unsafe { AzString::from_utf8(core::ptr::null(), 3) }.is_empty());
1479 assert!(unsafe { AzString::from_utf8(buf.as_ptr(), 0) }.is_empty());
1480 assert!(unsafe { AzString::from_utf8_lossy(core::ptr::null(), 3) }.is_empty());
1481 assert!(unsafe { AzString::from_utf8_lossy(buf.as_ptr(), 0) }.is_empty());
1482 }
1483
1484 #[test]
1485 fn azstring_from_utf8_accepts_valid_multibyte() {
1486 let buf = "héllo \u{1F600}".as_bytes();
1487 let s = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1488 assert_eq!(s.as_str(), "héllo \u{1F600}");
1489 assert_eq!(s.len(), buf.len());
1490 }
1491
1492 #[test]
1493 fn azstring_from_utf8_rejects_invalid_but_lossy_replaces_it() {
1494 let buf = b"caf\xC3";
1496 let strict = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1497 assert!(
1498 strict.is_empty(),
1499 "from_utf8 must return an EMPTY string for invalid UTF-8, got {:?}",
1500 strict.as_str()
1501 );
1502 let lossy = unsafe { AzString::from_utf8_lossy(buf.as_ptr(), buf.len()) };
1503 assert_eq!(lossy.as_str(), "caf\u{FFFD}");
1504 }
1505
1506 #[test]
1507 fn azstring_from_utf8_rejects_overlong_and_stray_continuations() {
1508 for bad in [
1509 &b"\xC0\xAF"[..], &b"\xED\xA0\x80"[..], &b"\xF8\x88\x80\x80"[..], &b"\x80"[..], &b"\xFF\xFE"[..], ] {
1515 let strict = unsafe { AzString::from_utf8(bad.as_ptr(), bad.len()) };
1516 assert!(strict.is_empty(), "from_utf8 must reject {bad:?}");
1517
1518 let lossy = unsafe { AzString::from_utf8_lossy(bad.as_ptr(), bad.len()) };
1519 assert!(
1520 lossy.as_str().contains('\u{FFFD}'),
1521 "from_utf8_lossy must substitute U+FFFD for {bad:?}"
1522 );
1523 assert!(core::str::from_utf8(lossy.as_bytes()).is_ok());
1525 }
1526 }
1527
1528 #[test]
1529 fn azstring_from_utf8_keeps_interior_nul() {
1530 let buf = b"a\0b";
1531 let s = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1532 assert_eq!(s.len(), 3);
1533 assert_eq!(s.as_bytes(), b"a\0b");
1534 }
1535
1536 #[test]
1537 fn azstring_from_utf8_handles_a_megabyte() {
1538 let buf = "y".repeat(1_000_000);
1539 let s = unsafe { AzString::from_utf8(buf.as_bytes().as_ptr(), buf.len()) };
1540 assert_eq!(s.len(), 1_000_000);
1541 }
1542
1543 #[test]
1548 fn azstring_from_utf16_le_decodes_bmp_and_surrogate_pairs() {
1549 let text = "héllo \u{1F600}"; let bytes = utf16_bytes(text, true);
1551 let s = unsafe { AzString::from_utf16_le(bytes.as_ptr(), bytes.len()) };
1552 assert_eq!(s.as_str(), text);
1553 }
1554
1555 #[test]
1556 fn azstring_from_utf16_be_decodes_bmp_and_surrogate_pairs() {
1557 let text = "héllo \u{1F600}";
1558 let bytes = utf16_bytes(text, false);
1559 let s = unsafe { AzString::from_utf16_be(bytes.as_ptr(), bytes.len()) };
1560 assert_eq!(s.as_str(), text);
1561 }
1562
1563 #[test]
1564 fn azstring_from_utf16_byte_order_actually_matters() {
1565 let le = utf16_bytes("AB", true);
1567 assert_eq!(le.as_slice(), &[0x41, 0x00, 0x42, 0x00]);
1568 let as_be = unsafe { AzString::from_utf16_be(le.as_ptr(), le.len()) };
1569 assert_eq!(
1570 as_be.as_str(),
1571 "\u{4100}\u{4200}",
1572 "BE decode of LE bytes must byte-swap, not guess"
1573 );
1574 assert_ne!(as_be.as_str(), "AB");
1575 }
1576
1577 #[test]
1578 fn azstring_from_utf16_odd_length_is_empty() {
1579 let bytes = utf16_bytes("hello", true);
1580 let odd = bytes.len() - 1;
1581 assert_eq!(odd % 2, 1);
1582 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), odd) }.is_empty());
1585 assert!(unsafe { AzString::from_utf16_be(bytes.as_ptr(), odd) }.is_empty());
1586 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), 1) }.is_empty());
1588 }
1589
1590 #[test]
1591 fn azstring_from_utf16_null_or_zero_len_is_empty() {
1592 let bytes = utf16_bytes("hi", true);
1593 assert!(unsafe { AzString::from_utf16_le(core::ptr::null(), 4) }.is_empty());
1594 assert!(unsafe { AzString::from_utf16_be(core::ptr::null(), 4) }.is_empty());
1595 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), 0) }.is_empty());
1596 assert!(unsafe { AzString::from_utf16_be(bytes.as_ptr(), 0) }.is_empty());
1597 }
1598
1599 #[test]
1600 fn azstring_from_utf16_unpaired_surrogate_is_empty() {
1601 let lone_high: [u8; 2] = 0xD83C_u16.to_le_bytes();
1603 assert!(unsafe { AzString::from_utf16_le(lone_high.as_ptr(), 2) }.is_empty());
1604
1605 let lone_low: [u8; 2] = 0xDF89_u16.to_le_bytes();
1607 assert!(unsafe { AzString::from_utf16_le(lone_low.as_ptr(), 2) }.is_empty());
1608
1609 let reversed: Vec<u8> = [0xDF89_u16, 0xD83C_u16]
1610 .iter()
1611 .flat_map(|u| u.to_le_bytes())
1612 .collect();
1613 assert!(unsafe { AzString::from_utf16_le(reversed.as_ptr(), reversed.len()) }.is_empty());
1614 }
1615
1616 #[test]
1617 fn azstring_from_utf16_decodes_noncharacters_and_nul() {
1618 let units: Vec<u8> = [0x0041_u16, 0x0000, 0xFFFE]
1621 .iter()
1622 .flat_map(|u| u.to_le_bytes())
1623 .collect();
1624 let s = unsafe { AzString::from_utf16_le(units.as_ptr(), units.len()) };
1625 assert_eq!(s.as_str(), "A\u{0}\u{FFFE}");
1626 assert_eq!(s.len(), 1 + 1 + 3);
1627 }
1628
1629 #[test]
1630 fn azstring_from_utf16_handles_100k_code_units() {
1631 let text = "ab".repeat(50_000);
1632 let bytes = utf16_bytes(&text, true);
1633 assert_eq!(bytes.len(), 200_000);
1634 let s = unsafe { AzString::from_utf16_le(bytes.as_ptr(), bytes.len()) };
1635 assert_eq!(s.len(), 100_000);
1636 }
1637
1638 #[test]
1639 fn azstring_from_utf16_with_byte_order_honours_the_supplied_fn() {
1640 fn swap_halves(b: [u8; 2]) -> u16 {
1642 u16::from_be_bytes(b)
1643 }
1644 let le = utf16_bytes("Az", true);
1645 let via_shared = unsafe {
1646 AzString::from_utf16_with_byte_order(le.as_ptr(), le.len(), u16::from_le_bytes)
1647 };
1648 assert_eq!(via_shared.as_str(), "Az");
1649
1650 let swapped = unsafe {
1651 AzString::from_utf16_with_byte_order(le.as_ptr(), le.len(), swap_halves)
1652 };
1653 assert_eq!(swapped.as_str(), "\u{4100}\u{7A00}");
1654
1655 assert!(unsafe {
1657 AzString::from_utf16_with_byte_order(le.as_ptr(), 3, u16::from_le_bytes)
1658 }
1659 .is_empty());
1660 assert!(unsafe {
1661 AzString::from_utf16_with_byte_order(core::ptr::null(), 2, u16::from_le_bytes)
1662 }
1663 .is_empty());
1664 }
1665
1666 #[test]
1671 fn azstring_as_str_and_as_bytes_agree_for_every_constructor() {
1672 let buf = "mixed \u{1F600}".as_bytes();
1673 let mut arena = StringArena::new();
1674 let strings = [
1675 AzString::default(),
1676 AzString::from_const_str("mixed \u{1F600}"),
1677 AzString::from_string(String::from("mixed \u{1F600}")),
1678 AzString::from("mixed \u{1F600}"),
1679 AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len()),
1680 unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) },
1681 arena.intern("mixed \u{1F600}"),
1682 ];
1683 for s in &strings {
1684 assert_eq!(
1685 s.as_bytes(),
1686 s.as_str().as_bytes(),
1687 "as_bytes() and as_str() must view the same memory"
1688 );
1689 assert_eq!(s.len(), s.as_bytes().len());
1690 assert_eq!(s.is_empty(), s.len() == 0);
1691 let via_as_ref: &str = s.as_ref();
1692 assert_eq!(via_as_ref, s.as_str(), "AsRef must match as_str");
1693 assert_eq!(&**s, s.as_str(), "Deref must match as_str");
1694 }
1695 }
1696
1697 #[test]
1698 fn azstring_is_empty_only_for_zero_bytes() {
1699 assert!(AzString::default().is_empty());
1700 assert!(AzString::from_const_str("").is_empty());
1701 assert!(AzString::from_string(String::new()).is_empty());
1702 assert!(!AzString::from_const_str(" ").is_empty());
1704 assert!(!AzString::from_const_str("\t\n").is_empty());
1705 assert!(!AzString::from_string(String::from("\0")).is_empty());
1706 assert_eq!(AzString::from_string(String::from("\0")).len(), 1);
1707 }
1708
1709 #[test]
1710 fn azstring_len_counts_bytes_not_chars() {
1711 assert_eq!(AzString::from_const_str("é").len(), 2);
1712 assert_eq!(AzString::from_const_str("\u{1F600}").len(), 4);
1713 assert_eq!(AzString::from_const_str("e\u{0301}").len(), 3); assert_eq!(AzString::from_const_str("\u{1F600}").as_str().chars().count(), 1);
1715 }
1716
1717 #[test]
1718 fn azstring_into_bytes_moves_without_copying_or_double_freeing() {
1719 let s = AzString::from_string(String::from("payload"));
1720 let ptr = s.as_bytes().as_ptr();
1721 let (len, cap) = (s.vec.len, s.vec.cap);
1722 let v = s.into_bytes();
1723 assert_eq!(v.as_ref(), b"payload");
1724 assert_eq!(v.as_ptr(), ptr, "into_bytes must move, not copy");
1725 assert_eq!(v.len(), len);
1726 assert_eq!(v.capacity(), cap);
1727 assert!(matches!(v.destructor, U8VecDestructor::DefaultRust));
1728 }
1731
1732 #[test]
1733 fn azstring_into_bytes_preserves_a_non_owning_destructor() {
1734 let v = AzString::from_const_str("static").into_bytes();
1735 assert_eq!(v.as_ref(), b"static");
1736 assert!(
1737 matches!(v.destructor, U8VecDestructor::NoDestructor),
1738 "a &'static-backed AzString must not gain a freeing destructor"
1739 );
1740 }
1741
1742 #[test]
1743 fn azstring_into_bytes_of_empty_is_empty() {
1744 let v = AzString::default().into_bytes();
1745 assert!(v.is_empty());
1746 assert_eq!(v.as_ref(), b"");
1747 }
1748
1749 #[test]
1750 fn azstring_into_library_owned_string_works_for_all_destructors() {
1751 assert_eq!(
1753 AzString::from_string(String::from("owned \u{1F600}")).into_library_owned_string(),
1754 "owned \u{1F600}"
1755 );
1756 assert_eq!(
1758 AzString::from_const_str("static").into_library_owned_string(),
1759 "static"
1760 );
1761 let owned = {
1763 let mut arena = StringArena::new();
1764 let s = arena.intern("interned");
1765 s.into_library_owned_string()
1766 };
1767 assert_eq!(owned, "interned", "must outlive the arena it was copied from");
1768 assert_eq!(AzString::default().into_library_owned_string(), "");
1770 }
1771
1772 #[test]
1773 fn azstring_into_library_owned_string_copies_static_memory() {
1774 let mut owned = AzString::from_const_str("static").into_library_owned_string();
1775 owned.push_str(" + mutable");
1778 assert_eq!(owned, "static + mutable");
1779 }
1780
1781 #[test]
1786 fn azstring_clone_self_deep_copies_library_owned_memory() {
1787 let s = AzString::from_string(String::from("deep"));
1788 let c = s.clone_self();
1789 assert_eq!(c, s);
1790 assert_ne!(
1791 c.as_bytes().as_ptr(),
1792 s.as_bytes().as_ptr(),
1793 "a DefaultRust clone must own a fresh allocation"
1794 );
1795 assert!(matches!(c.vec.destructor, U8VecDestructor::DefaultRust));
1796 }
1797
1798 #[test]
1799 fn azstring_clone_self_shares_static_memory() {
1800 let s = AzString::from_const_str("static");
1801 let c = s.clone_self();
1802 assert_eq!(c, s);
1803 assert_eq!(
1804 c.as_bytes().as_ptr(),
1805 s.as_bytes().as_ptr(),
1806 "cloning a &'static-backed string should alias, not allocate"
1807 );
1808 assert!(matches!(c.vec.destructor, U8VecDestructor::NoDestructor));
1809 }
1810
1811 #[test]
1812 fn azstring_clone_self_of_empty_and_unicode() {
1813 for s in [
1814 AzString::default(),
1815 AzString::from_const_str(""),
1816 AzString::from_string(String::from("\u{1F600}\u{0}\u{0301}")),
1817 ] {
1818 let c = s.clone_self();
1819 assert_eq!(c.as_str(), s.as_str());
1820 assert_eq!(c.len(), s.len());
1821 }
1822 }
1823
1824 #[test]
1825 fn azstring_clone_trait_matches_clone_self() {
1826 let s = AzString::from_string(String::from("via trait"));
1827 assert_eq!(s.clone(), s.clone_self());
1828 }
1829
1830 #[test]
1835 fn azstring_display_round_trips_through_from() {
1836 for original in [
1837 "",
1838 " ",
1839 "plain",
1840 "héllo \u{1F600}",
1841 "with \"quotes\" and \\ backslash",
1842 "line\nbreak\ttab",
1843 "e\u{0301} combining",
1844 ] {
1845 let s = AzString::from(original);
1846 let rendered = format!("{s}");
1847 assert_eq!(rendered, original, "Display must emit the string verbatim");
1848 let reparsed = AzString::from(rendered.as_str());
1849 assert_eq!(reparsed, s, "parse(serialize(x)) == x");
1850 assert_eq!(format!("{reparsed}"), rendered);
1852 }
1853 }
1854
1855 #[test]
1856 fn azstring_debug_matches_str_debug_and_escapes() {
1857 let s = AzString::from("a\"b\\c\nd");
1858 let expected = format!("{:?}", "a\"b\\c\nd");
1859 assert_eq!(format!("{s:?}"), expected, "Debug must delegate to str::fmt");
1860 assert!(format!("{s:?}").starts_with('"'), "Debug output must be quoted");
1861 assert!(!format!("{s:?}").contains('\n'), "Debug must escape newlines");
1862 }
1863
1864 #[test]
1865 fn azstring_debug_and_display_of_empty_do_not_panic() {
1866 assert_eq!(format!("{:?}", AzString::default()), "\"\"");
1867 assert_eq!(format!("{}", AzString::default()), "");
1868 assert_eq!(format!("{:?}", AzString::from_const_str("")), "\"\"");
1869 }
1870
1871 #[test]
1872 fn azstring_display_of_a_megabyte_is_lossless() {
1873 let huge = "q".repeat(1_000_000);
1874 let s = AzString::from_string(huge.clone());
1875 assert_eq!(format!("{s}").len(), huge.len());
1876 }
1877
1878 #[test]
1879 fn azstring_debug_is_stable_across_constructors() {
1880 let buf = "same".as_bytes();
1882 let a = AzString::from_const_str("same");
1883 let b = AzString::from_string(String::from("same"));
1884 let c = AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1885 assert_eq!(format!("{a:?}"), format!("{b:?}"));
1886 assert_eq!(format!("{b:?}"), format!("{c:?}"));
1887 assert_eq!(format!("{a}"), format!("{c}"));
1888 }
1889
1890 #[test]
1895 fn azstring_eq_and_hash_ignore_memory_ownership() {
1896 let buf = "key".as_bytes();
1897 let mut arena = StringArena::new();
1898 let variants = [
1899 AzString::from_const_str("key"),
1900 AzString::from_string(String::from("key")),
1901 AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len()),
1902 arena.intern("key"),
1903 ];
1904 for v in &variants {
1905 assert_eq!(*v, variants[0], "equality must compare CONTENT, not pointers");
1906 assert_eq!(
1907 hash_of(v),
1908 hash_of(&variants[0]),
1909 "Hash must agree with Eq across destructor kinds"
1910 );
1911 assert_eq!(
1912 hash_of(v),
1913 hash_of(&"key"),
1914 "AzString must hash like the &str it wraps"
1915 );
1916 }
1917 }
1918
1919 #[test]
1920 fn azstring_ord_matches_str_ord() {
1921 let mut v = [
1922 AzString::from("b"),
1923 AzString::from(""),
1924 AzString::from("\u{1F600}"),
1925 AzString::from("a"),
1926 AzString::from("ab"),
1927 ];
1928 v.sort();
1929 let sorted: Vec<&str> = v.iter().map(AzString::as_str).collect();
1930 assert_eq!(sorted, ["", "a", "ab", "b", "\u{1F600}"]);
1931 assert_eq!(
1932 AzString::from("a").partial_cmp(&AzString::from("b")),
1933 Some(core::cmp::Ordering::Less)
1934 );
1935 assert_eq!(
1936 AzString::from("x").cmp(&AzString::from("x")),
1937 core::cmp::Ordering::Equal
1938 );
1939 }
1940
1941 #[test]
1946 fn arena_new_starts_empty() {
1947 let arena = StringArena::new();
1948 assert_eq!(arena.metrics(), (0, 0), "a fresh arena allocates nothing");
1949 assert_eq!(StringArena::default().metrics(), (0, 0));
1950 }
1951
1952 #[test]
1953 fn arena_metrics_track_chunks_and_bytes() {
1954 let mut arena = StringArena::new();
1955 let _a = arena.intern("abc");
1956 let (chunks, bytes) = arena.metrics();
1957 assert_eq!(chunks, 1);
1958 assert_eq!(bytes, 3);
1959 let _b = arena.intern("de");
1960 let (chunks, bytes) = arena.metrics();
1961 assert_eq!(chunks, 1, "a second small string reuses the open chunk");
1962 assert_eq!(bytes, 5);
1963 }
1964
1965 #[test]
1966 fn arena_empty_string_allocates_nothing_and_is_readable() {
1967 let mut arena = StringArena::new();
1968 let e = arena.intern("");
1969 assert!(e.is_empty());
1970 assert_eq!(e.as_str(), "");
1971 assert_eq!(arena.metrics(), (0, 0), "empty strings need no storage");
1972 assert!(!e.vec.ptr.is_null(), "the dangling ptr must still be non-null");
1973 }
1974
1975 #[test]
1976 fn arena_string_is_external_and_stashes_an_arc_in_cap() {
1977 let mut arena = StringArena::new();
1978 let s = arena.intern("hi");
1979 assert!(matches!(s.vec.destructor, U8VecDestructor::External(_)));
1980 assert_ne!(s.vec.cap, 0, "cap holds the Arc pointer, not a capacity");
1981 assert_eq!(s.as_str(), "hi");
1982 }
1983
1984 #[test]
1985 fn arena_intern_refcounts_each_string() {
1986 let mut arena = StringArena::new();
1987 assert_eq!(Arc::strong_count(&arena.inner), 1);
1988 let a = arena.intern("one");
1989 let b = arena.intern("two");
1990 assert_eq!(
1991 Arc::strong_count(&arena.inner),
1992 3,
1993 "each interned string must hold its own Arc reference"
1994 );
1995 drop(a);
1996 assert_eq!(Arc::strong_count(&arena.inner), 2);
1997 drop(b);
1998 assert_eq!(Arc::strong_count(&arena.inner), 1);
1999 }
2000
2001 #[test]
2002 fn arena_clone_deep_copies_and_does_not_bump_the_refcount() {
2003 let mut arena = StringArena::new();
2004 let s = arena.intern("interned");
2005 let c = s.clone_self();
2006 assert_eq!(
2007 Arc::strong_count(&arena.inner),
2008 2,
2009 "cloning an External string deep-copies; it must NOT retain the arena"
2010 );
2011 assert!(matches!(c.vec.destructor, U8VecDestructor::DefaultRust));
2012 assert_eq!(c.as_str(), "interned");
2013 assert_ne!(c.vec.ptr, s.vec.ptr);
2014 }
2015
2016 #[test]
2017 fn arena_clone_outlives_the_arena_and_the_original() {
2018 let clone = {
2019 let mut arena = StringArena::new();
2020 let s = arena.intern("deep-copied out of the arena");
2021 let c = s.clone_self();
2022 drop(s);
2023 drop(arena);
2024 c
2025 };
2026 assert_eq!(clone.as_str(), "deep-copied out of the arena");
2027 }
2028
2029 #[test]
2030 fn arena_exact_half_chunk_boundary_fills_one_chunk_exactly() {
2031 let mut arena = StringArena::new();
2035 let half = "h".repeat(StringArena::CHUNK_SIZE / 2);
2036 let a = arena.intern(&half);
2037 let b = arena.intern(&half);
2038 assert_eq!(arena.metrics().0, 1, "two half-chunks must share one chunk");
2039 let c = arena.intern(&half);
2040 assert_eq!(arena.metrics().0, 2, "the third must open a new chunk");
2041
2042 assert_eq!(a.as_str(), half);
2044 assert_eq!(b.as_str(), half);
2045 assert_eq!(c.as_str(), half);
2046 }
2047
2048 #[test]
2049 fn arena_oversized_string_is_readable_and_gets_its_own_chunk() {
2050 let mut arena = StringArena::new();
2051 let big = "b".repeat(StringArena::CHUNK_SIZE + 1);
2052 let s = arena.intern(&big);
2053 assert_eq!(s.len(), big.len());
2054 assert_eq!(s.as_str(), big.as_str());
2055 assert_eq!(arena.metrics(), (1, big.len()));
2056 }
2057
2058 #[test]
2059 fn arena_many_interleaved_sizes_all_read_back_correctly() {
2060 let mut arena = StringArena::new();
2061 let mut kept = Vec::new();
2062 for i in 0..200 {
2063 let s = format!("s{i}-{}", "p".repeat(i % 17));
2064 kept.push((arena.intern(&s), s));
2065 }
2066 for (interned, expected) in &kept {
2067 assert_eq!(interned.as_str(), expected.as_str());
2068 }
2069 }
2070
2071 #[test]
2072 fn arena_interns_unicode_and_nul_bytes_verbatim() {
2073 let mut arena = StringArena::new();
2074 let weird = "héllo \u{1F600}\u{0}\u{0301}";
2075 let s = arena.intern(weird);
2076 assert_eq!(s.as_str(), weird);
2077 assert_eq!(s.len(), weird.len());
2078 }
2079
2080 #[test]
2081 fn arena_strings_outlive_the_handle_even_when_interleaved() {
2082 let (a, b) = {
2083 let mut arena = StringArena::new();
2084 let a = arena.intern("first");
2085 let big = "z".repeat(StringArena::CHUNK_SIZE * 2);
2086 let _dropped = arena.intern(&big);
2087 let b = arena.intern("second");
2088 (a, b)
2089 };
2090 assert_eq!(a.as_str(), "first");
2091 assert_eq!(b.as_str(), "second");
2092 }
2093
2094 #[test]
2108 fn arena_small_after_oversized_must_not_grow_the_full_dedicated_chunk() {
2109 let mut arena = StringArena::new();
2110
2111 let _small = arena.intern("a");
2113
2114 let big = "x".repeat(StringArena::CHUNK_SIZE);
2116 let big_len = big.len();
2117 let interned_big = arena.intern(&big);
2118 assert_eq!(interned_big.as_str(), big.as_str(), "valid before the next intern");
2119
2120 let _small2 = arena.intern("y");
2122
2123 let grew = unsafe {
2126 let chunks = &*arena.inner.chunks.get();
2127 chunks.iter().any(|c| c.len() > big_len)
2128 };
2129 assert!(
2130 !grew,
2131 "intern() appended a small string into the FULL dedicated chunk of an oversized \
2132 string (len == cap), which reallocates that Vec and leaves every AzString pointing \
2133 into it dangling — a use-after-free. Root cause: the oversized branch pushes a chunk \
2134 without resetting `current_remaining`, so the next small string takes the \
2135 `chunks.last_mut()` fast path onto the wrong chunk."
2136 );
2137 }
2138
2139 #[test]
2144 fn arena_destructor_drops_one_arc_ref_and_is_idempotent() {
2145 let inner = Arc::new(StringArenaInner {
2146 chunks: UnsafeCell::new(Vec::new()),
2147 current_remaining: UnsafeCell::new(0),
2148 });
2149 let raw = Arc::into_raw(Arc::clone(&inner));
2150 assert_eq!(Arc::strong_count(&inner), 2);
2151
2152 let mut v = U8Vec {
2153 ptr: core::ptr::NonNull::<u8>::dangling().as_ptr().cast_const(),
2154 len: 0,
2155 cap: raw as usize,
2156 destructor: U8VecDestructor::External(arena_string_destructor),
2157 };
2158
2159 arena_string_destructor(&mut v);
2160 assert_eq!(
2161 Arc::strong_count(&inner),
2162 1,
2163 "the destructor must release exactly one Arc reference"
2164 );
2165 assert_eq!(v.cap, 0, "cap must be zeroed to guard against a double drop");
2166
2167 arena_string_destructor(&mut v);
2169 assert_eq!(Arc::strong_count(&inner), 1);
2170 assert_eq!(v.cap, 0);
2171
2172 drop(v);
2175 assert_eq!(Arc::strong_count(&inner), 1);
2176 }
2177
2178 #[test]
2179 fn arena_destructor_tolerates_a_null_arc_pointer() {
2180 let mut v = U8Vec {
2182 ptr: core::ptr::null(),
2183 len: 0,
2184 cap: 0,
2185 destructor: U8VecDestructor::NoDestructor,
2186 };
2187 arena_string_destructor(&mut v);
2188 assert_eq!(v.cap, 0);
2189 }
2190
2191 #[test]
2192 fn arena_last_reference_frees_the_chunks() {
2193 let inner_ptr;
2196 let s = {
2197 let mut arena = StringArena::new();
2198 let s = arena.intern("outlives the handle");
2199 inner_ptr = Arc::as_ptr(&arena.inner);
2200 assert_eq!(Arc::strong_count(&arena.inner), 2);
2201 s
2202 };
2203 assert_eq!(s.as_str(), "outlives the handle");
2205 assert_eq!(s.vec.cap as *const StringArenaInner, inner_ptr);
2206 drop(s); }
2208}