1use crate::analysis::unsafe_ffi_tracker::{
8 get_global_unsafe_ffi_tracker, EnhancedAllocationInfo, UnsafeFFIStats,
9};
10use crate::core::scope_tracker::get_global_scope_tracker;
11use crate::core::tracker::get_global_tracker;
12use crate::core::types::MemoryStats;
13use crate::core::types::ScopeInfo;
14use crate::core::types::{AllocationInfo, TrackingError, TrackingResult};
15use std::time::{Duration, Instant};
16
17pub struct DataLocalizer {
19 cached_allocations: Option<Vec<AllocationInfo>>,
21 cached_ffi_data: Option<Vec<EnhancedAllocationInfo>>,
23 cached_stats: Option<MemoryStats>,
25 cached_ffi_stats: Option<UnsafeFFIStats>,
27 cached_scope_info: Option<Vec<ScopeInfo>>,
29 last_update: Instant,
31 cache_ttl: Duration,
33}
34
35#[derive(Debug, Clone)]
37pub struct LocalizedExportData {
38 pub allocations: Vec<AllocationInfo>,
40 pub enhanced_allocations: Vec<EnhancedAllocationInfo>,
42 pub stats: MemoryStats,
44 pub ffi_stats: UnsafeFFIStats,
46 pub scope_info: Vec<ScopeInfo>,
48 pub timestamp: Instant,
50}
51
52#[derive(Debug, Clone)]
54pub struct DataGatheringStats {
55 pub total_time_ms: u64,
57 pub basic_data_time_ms: u64,
59 pub ffi_data_time_ms: u64,
61 pub scope_data_time_ms: u64,
63 pub allocation_count: usize,
65 pub ffi_allocation_count: usize,
67 pub scope_count: usize,
69}
70
71impl DataLocalizer {
72 pub fn new() -> Self {
74 Self {
75 cached_allocations: None,
76 cached_ffi_data: None,
77 cached_stats: None,
78 cached_ffi_stats: None,
79 cached_scope_info: None,
80 last_update: Instant::now(),
81 cache_ttl: Duration::from_millis(100), }
83 }
84
85 pub fn with_cache_ttl(cache_ttl: Duration) -> Self {
87 Self {
88 cached_allocations: None,
89 cached_ffi_data: None,
90 cached_stats: None,
91 cached_ffi_stats: None,
92 cached_scope_info: None,
93 last_update: Instant::now(),
94 cache_ttl,
95 }
96 }
97
98 pub fn gather_all_export_data(
100 &mut self,
101 ) -> TrackingResult<(LocalizedExportData, DataGatheringStats)> {
102 let total_start = Instant::now();
103
104 tracing::info!("🔄 start data localization to reduce global state access...");
105
106 if self.is_cache_valid() {
108 tracing::info!("✅ using cached data, skipping repeated fetching");
109 return self.get_cached_data();
110 }
111
112 let basic_start = Instant::now();
114 let tracker = get_global_tracker();
115
116 let allocations = self.get_allocations_with_timeout(&tracker)?;
118 let stats = self.get_stats_with_timeout(&tracker)?;
119 let basic_time = basic_start.elapsed();
120
121 let ffi_start = Instant::now();
123 let ffi_tracker = get_global_unsafe_ffi_tracker();
124 let enhanced_allocations = self.get_ffi_allocations_with_timeout(&ffi_tracker);
125 let ffi_stats = self.get_ffi_stats_with_timeout(&ffi_tracker);
126 let ffi_time = ffi_start.elapsed();
127
128 let scope_start = Instant::now();
130 let scope_tracker = get_global_scope_tracker();
131 let scope_info = self.get_scope_info_with_timeout(&scope_tracker);
132 let scope_time = scope_start.elapsed();
133
134 let total_time = total_start.elapsed();
135
136 self.cached_allocations = Some(allocations.clone());
138 self.cached_ffi_data = Some(enhanced_allocations.clone());
139 self.cached_stats = Some(stats.clone());
140 self.cached_ffi_stats = Some(ffi_stats.clone());
141 self.cached_scope_info = Some(scope_info.clone());
142 self.last_update = Instant::now();
143
144 let localized_data = LocalizedExportData {
145 allocations: allocations.clone(),
146 enhanced_allocations: enhanced_allocations.clone(),
147 stats,
148 ffi_stats,
149 scope_info: scope_info.clone(),
150 timestamp: total_start,
151 };
152
153 let gathering_stats = DataGatheringStats {
154 total_time_ms: total_time.as_millis() as u64,
155 basic_data_time_ms: basic_time.as_millis() as u64,
156 ffi_data_time_ms: ffi_time.as_millis() as u64,
157 scope_data_time_ms: scope_time.as_millis() as u64,
158 allocation_count: allocations.len(),
159 ffi_allocation_count: enhanced_allocations.len(),
160 scope_count: scope_info.len(),
161 };
162
163 tracing::info!("✅ data localization completed:");
165 tracing::info!(" total time: {:?}", total_time);
166 tracing::info!(
167 " basic data: {:?} ({} allocations)",
168 basic_time,
169 gathering_stats.allocation_count
170 );
171 tracing::info!(
172 " ffi data: {:?} ({} enhanced allocations)",
173 ffi_time,
174 gathering_stats.ffi_allocation_count
175 );
176 tracing::info!(
177 " scope data: {:?} ({} scopes)",
178 scope_time,
179 gathering_stats.scope_count
180 );
181 tracing::info!(
182 " data localization avoided {} global state accesses",
183 self.estimate_avoided_global_accesses(&gathering_stats)
184 );
185
186 Ok((localized_data, gathering_stats))
187 }
188
189 pub fn refresh_cache(&mut self) -> TrackingResult<(LocalizedExportData, DataGatheringStats)> {
191 self.invalidate_cache();
192 self.gather_all_export_data()
193 }
194
195 fn is_cache_valid(&self) -> bool {
197 self.cached_allocations.is_some()
198 && self.cached_ffi_data.is_some()
199 && self.cached_stats.is_some()
200 && self.cached_ffi_stats.is_some()
201 && self.cached_scope_info.is_some()
202 && self.last_update.elapsed() < self.cache_ttl
203 }
204
205 fn get_cached_data(&self) -> TrackingResult<(LocalizedExportData, DataGatheringStats)> {
207 let localized_data = LocalizedExportData {
208 allocations: self
209 .cached_allocations
210 .as_ref()
211 .ok_or_else(|| {
212 TrackingError::InternalError("Cached allocations not available".to_string())
213 })?
214 .clone(),
215 enhanced_allocations: self
216 .cached_ffi_data
217 .as_ref()
218 .ok_or_else(|| {
219 TrackingError::InternalError("Cached FFI data not available".to_string())
220 })?
221 .clone(),
222 stats: self
223 .cached_stats
224 .as_ref()
225 .ok_or_else(|| {
226 TrackingError::InternalError("Cached stats not available".to_string())
227 })?
228 .clone(),
229 ffi_stats: self
230 .cached_ffi_stats
231 .as_ref()
232 .ok_or_else(|| {
233 TrackingError::InternalError("Cached FFI stats not available".to_string())
234 })?
235 .clone(),
236 scope_info: self
237 .cached_scope_info
238 .as_ref()
239 .ok_or_else(|| {
240 TrackingError::InternalError("Cached scope info not available".to_string())
241 })?
242 .clone(),
243 timestamp: self.last_update,
244 };
245
246 let gathering_stats = DataGatheringStats {
247 total_time_ms: 0, basic_data_time_ms: 0,
249 ffi_data_time_ms: 0,
250 scope_data_time_ms: 0,
251 allocation_count: localized_data.allocations.len(),
252 ffi_allocation_count: localized_data.enhanced_allocations.len(),
253 scope_count: localized_data.scope_info.len(),
254 };
255
256 Ok((localized_data, gathering_stats))
257 }
258
259 pub fn invalidate_cache(&mut self) {
261 self.cached_allocations = None;
262 self.cached_ffi_data = None;
263 self.cached_stats = None;
264 self.cached_ffi_stats = None;
265 self.cached_scope_info = None;
266 }
267
268 fn estimate_avoided_global_accesses(&self, stats: &DataGatheringStats) -> usize {
270 let basic_accesses = stats.allocation_count * 2; let ffi_accesses = stats.ffi_allocation_count * 3; let scope_accesses = stats.scope_count; basic_accesses + ffi_accesses + scope_accesses
277 }
278
279 pub fn get_cache_stats(&self) -> CacheStats {
281 CacheStats {
282 is_cached: self.is_cache_valid(),
283 cache_age_ms: self.last_update.elapsed().as_millis() as u64,
284 cache_ttl_ms: self.cache_ttl.as_millis() as u64,
285 cached_allocation_count: self
286 .cached_allocations
287 .as_ref()
288 .map(|v| v.len())
289 .unwrap_or(0),
290 cached_ffi_count: self.cached_ffi_data.as_ref().map(|v| v.len()).unwrap_or(0),
291 cached_scope_count: self
292 .cached_scope_info
293 .as_ref()
294 .map(|v| v.len())
295 .unwrap_or(0),
296 }
297 }
298
299 fn get_allocations_with_timeout(
301 &self,
302 tracker: &std::sync::Arc<crate::core::tracker::MemoryTracker>,
303 ) -> TrackingResult<Vec<AllocationInfo>> {
304 use std::thread;
305 use std::time::Duration;
306
307 const MAX_RETRIES: u32 = 5;
308 const RETRY_DELAY: Duration = Duration::from_millis(10);
309
310 for attempt in 0..MAX_RETRIES {
311 match tracker.get_active_allocations() {
312 Ok(allocations) => return Ok(allocations),
313 Err(e) => {
314 if attempt == MAX_RETRIES - 1 {
315 tracing::warn!(
316 "Failed to get allocations after {} attempts: {}",
317 MAX_RETRIES,
318 e
319 );
320 return Ok(Vec::new()); }
322 thread::sleep(RETRY_DELAY * (attempt + 1));
323 }
324 }
325 }
326 Ok(Vec::new())
327 }
328
329 fn get_stats_with_timeout(
331 &self,
332 tracker: &std::sync::Arc<crate::core::tracker::MemoryTracker>,
333 ) -> TrackingResult<MemoryStats> {
334 use std::thread;
335 use std::time::Duration;
336
337 const MAX_RETRIES: u32 = 5;
338 const RETRY_DELAY: Duration = Duration::from_millis(10);
339
340 for attempt in 0..MAX_RETRIES {
341 match tracker.get_stats() {
342 Ok(stats) => return Ok(stats),
343 Err(e) => {
344 if attempt == MAX_RETRIES - 1 {
345 tracing::warn!("Failed to get stats after {} attempts: {}", MAX_RETRIES, e);
346 return Ok(MemoryStats::default()); }
348 thread::sleep(RETRY_DELAY * (attempt + 1));
349 }
350 }
351 }
352 Ok(MemoryStats::default())
353 }
354
355 fn get_ffi_allocations_with_timeout(
357 &self,
358 ffi_tracker: &std::sync::Arc<crate::analysis::unsafe_ffi_tracker::UnsafeFFITracker>,
359 ) -> Vec<EnhancedAllocationInfo> {
360 use std::thread;
361 use std::time::Duration;
362
363 const MAX_RETRIES: u32 = 3;
364 const RETRY_DELAY: Duration = Duration::from_millis(5);
365
366 for attempt in 0..MAX_RETRIES {
367 match ffi_tracker.get_enhanced_allocations() {
368 Ok(allocations) => return allocations,
369 Err(e) => {
370 if attempt == MAX_RETRIES - 1 {
371 tracing::warn!(
372 "Failed to get FFI allocations after {} attempts: {}, using empty data",
373 MAX_RETRIES,
374 e
375 );
376 return Vec::new();
377 }
378 thread::sleep(RETRY_DELAY * (attempt + 1));
379 }
380 }
381 }
382 Vec::new()
383 }
384
385 fn get_ffi_stats_with_timeout(
387 &self,
388 ffi_tracker: &std::sync::Arc<crate::analysis::unsafe_ffi_tracker::UnsafeFFITracker>,
389 ) -> UnsafeFFIStats {
390 use std::thread;
391 use std::time::Duration;
392
393 const MAX_RETRIES: u32 = 3;
394 const RETRY_DELAY: Duration = Duration::from_millis(5);
395
396 for attempt in 0..MAX_RETRIES {
397 let stats = ffi_tracker.get_stats();
398 if attempt == 0 {
399 return stats; }
401 thread::sleep(RETRY_DELAY * (attempt + 1));
402 }
403 ffi_tracker.get_stats()
404 }
405
406 fn get_scope_info_with_timeout(
408 &self,
409 scope_tracker: &std::sync::Arc<crate::core::scope_tracker::ScopeTracker>,
410 ) -> Vec<ScopeInfo> {
411 use std::thread;
412 use std::time::Duration;
413
414 const MAX_RETRIES: u32 = 3;
415 const RETRY_DELAY: Duration = Duration::from_millis(5);
416
417 for attempt in 0..MAX_RETRIES {
418 let scope_info = scope_tracker.get_all_scopes();
419 if attempt == 0 {
420 return scope_info; }
422 thread::sleep(RETRY_DELAY * (attempt + 1));
423 }
424 scope_tracker.get_all_scopes()
425 }
426}
427
428#[derive(Debug, Clone)]
430pub struct CacheStats {
431 pub is_cached: bool,
433 pub cache_age_ms: u64,
435 pub cache_ttl_ms: u64,
437 pub cached_allocation_count: usize,
439 pub cached_ffi_count: usize,
441 pub cached_scope_count: usize,
443}
444
445impl Default for DataLocalizer {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451impl LocalizedExportData {
452 pub fn age(&self) -> Duration {
454 self.timestamp.elapsed()
455 }
456
457 pub fn is_fresh(&self, max_age: Duration) -> bool {
459 self.age() < max_age
460 }
461
462 pub fn total_allocation_count(&self) -> usize {
464 self.allocations.len() + self.enhanced_allocations.len()
465 }
466
467 pub fn get_summary(&self) -> String {
469 format!(
470 "LocalizedExportData {{ allocations: {}, ffi_allocations: {}, scopes: {}, age: {:?} }}",
471 self.allocations.len(),
472 self.enhanced_allocations.len(),
473 self.scope_info.len(),
474 self.age()
475 )
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::analysis::unsafe_ffi_tracker::{
483 AllocationSource, EnhancedAllocationInfo, UnsafeFFIStats,
484 };
485 use crate::core::types::{AllocationInfo, MemoryStats, ScopeInfo};
486 use crate::core::CallStackRef;
487 use std::time::{Duration, Instant};
488
489 fn create_test_enhanced_allocation_info(ptr: usize, size: usize) -> EnhancedAllocationInfo {
491 let base = AllocationInfo::new(ptr, size);
492 let call_stack = CallStackRef::new(0, Some(1));
493
494 EnhancedAllocationInfo {
495 base,
496 source: AllocationSource::UnsafeRust {
497 unsafe_block_location: "test_location".to_string(),
498 call_stack: call_stack.clone(),
499 risk_assessment: crate::analysis::unsafe_ffi_tracker::RiskAssessment {
500 risk_level: crate::analysis::unsafe_ffi_tracker::RiskLevel::Low,
501 risk_factors: vec![],
502 mitigation_suggestions: vec![],
503 confidence_score: 0.8,
504 assessment_timestamp: 0,
505 },
506 },
507 call_stack,
508 cross_boundary_events: vec![],
509 safety_violations: vec![],
510 ffi_tracked: false,
511 memory_passport: None,
512 ownership_history: None,
513 }
514 }
515
516 fn create_test_scope_info(name: &str) -> ScopeInfo {
518 ScopeInfo {
519 name: name.to_string(),
520 parent: None,
521 children: vec![],
522 depth: 0,
523 variables: vec![],
524 total_memory: 0,
525 peak_memory: 0,
526 allocation_count: 0,
527 lifetime_start: None,
528 lifetime_end: None,
529 is_active: true,
530 start_time: 0,
531 end_time: None,
532 memory_usage: 0,
533 child_scopes: vec![],
534 parent_scope: None,
535 }
536 }
537
538 #[test]
539 fn test_data_localizer_creation() {
540 let localizer = DataLocalizer::new();
541 assert!(!localizer.is_cache_valid());
542
543 let cache_stats = localizer.get_cache_stats();
544 assert!(!cache_stats.is_cached);
545 assert_eq!(cache_stats.cached_allocation_count, 0);
546 assert_eq!(cache_stats.cached_ffi_count, 0);
547 assert_eq!(cache_stats.cached_scope_count, 0);
548 assert_eq!(cache_stats.cache_ttl_ms, 100); }
550
551 #[test]
552 fn test_data_localizer_with_custom_ttl() {
553 let custom_ttl = Duration::from_millis(500);
554 let localizer = DataLocalizer::with_cache_ttl(custom_ttl);
555
556 let cache_stats = localizer.get_cache_stats();
557 assert_eq!(cache_stats.cache_ttl_ms, 500);
558 assert!(!cache_stats.is_cached);
559 }
560
561 #[test]
562 fn test_cache_ttl() {
563 let short_ttl = Duration::from_millis(1);
564 let mut localizer = DataLocalizer::with_cache_ttl(short_ttl);
565
566 localizer.cached_allocations = Some(vec![]);
568 localizer.cached_ffi_data = Some(vec![]);
569 localizer.cached_stats = Some(MemoryStats::default());
570 localizer.cached_ffi_stats = Some(UnsafeFFIStats::default());
571 localizer.cached_scope_info = Some(vec![]);
572 localizer.last_update = Instant::now();
573
574 assert!(localizer.is_cache_valid());
575
576 localizer.last_update = Instant::now() - Duration::from_millis(10);
578 assert!(!localizer.is_cache_valid());
579 }
580
581 #[test]
582 fn test_cache_validity_partial_data() {
583 let mut localizer = DataLocalizer::new();
584
585 localizer.cached_allocations = Some(vec![]);
587 localizer.cached_ffi_data = Some(vec![]);
588 localizer.last_update = Instant::now();
590
591 assert!(!localizer.is_cache_valid());
592 }
593
594 #[test]
595 fn test_invalidate_cache() {
596 let mut localizer = DataLocalizer::new();
597
598 localizer.cached_allocations = Some(vec![]);
600 localizer.cached_ffi_data = Some(vec![]);
601 localizer.cached_stats = Some(MemoryStats::default());
602 localizer.cached_ffi_stats = Some(UnsafeFFIStats::default());
603 localizer.cached_scope_info = Some(vec![]);
604 localizer.last_update = Instant::now();
605
606 assert!(localizer.is_cache_valid());
607
608 localizer.invalidate_cache();
609
610 assert!(!localizer.is_cache_valid());
611 assert!(localizer.cached_allocations.is_none());
612 assert!(localizer.cached_ffi_data.is_none());
613 assert!(localizer.cached_stats.is_none());
614 assert!(localizer.cached_ffi_stats.is_none());
615 assert!(localizer.cached_scope_info.is_none());
616 }
617
618 #[test]
619 fn test_estimate_avoided_global_accesses() {
620 let localizer = DataLocalizer::new();
621
622 let stats = DataGatheringStats {
623 total_time_ms: 100,
624 basic_data_time_ms: 50,
625 ffi_data_time_ms: 30,
626 scope_data_time_ms: 20,
627 allocation_count: 10,
628 ffi_allocation_count: 5,
629 scope_count: 3,
630 };
631
632 let avoided = localizer.estimate_avoided_global_accesses(&stats);
633 assert_eq!(avoided, 38);
635 }
636
637 #[test]
638 fn test_estimate_avoided_global_accesses_zero() {
639 let localizer = DataLocalizer::new();
640
641 let stats = DataGatheringStats {
642 total_time_ms: 0,
643 basic_data_time_ms: 0,
644 ffi_data_time_ms: 0,
645 scope_data_time_ms: 0,
646 allocation_count: 0,
647 ffi_allocation_count: 0,
648 scope_count: 0,
649 };
650
651 let avoided = localizer.estimate_avoided_global_accesses(&stats);
652 assert_eq!(avoided, 0);
653 }
654
655 #[test]
656 fn test_get_cache_stats_empty() {
657 let localizer = DataLocalizer::new();
658 let stats = localizer.get_cache_stats();
659
660 assert!(!stats.is_cached);
661 assert_eq!(stats.cached_allocation_count, 0);
662 assert_eq!(stats.cached_ffi_count, 0);
663 assert_eq!(stats.cached_scope_count, 0);
664 assert_eq!(stats.cache_ttl_ms, 100);
665 }
667
668 #[test]
669 fn test_get_cache_stats_with_data() {
670 let mut localizer = DataLocalizer::new();
671
672 localizer.cached_allocations = Some(vec![
674 AllocationInfo::new(0x1000, 256),
675 AllocationInfo::new(0x2000, 512),
676 AllocationInfo::new(0x3000, 1024),
677 ]);
678 localizer.cached_ffi_data = Some(vec![
679 create_test_enhanced_allocation_info(0x4000, 128),
680 create_test_enhanced_allocation_info(0x5000, 256),
681 ]);
682 localizer.cached_scope_info = Some(vec![create_test_scope_info("test_scope")]);
683 localizer.cached_stats = Some(MemoryStats::default());
684 localizer.cached_ffi_stats = Some(UnsafeFFIStats::default());
685 localizer.last_update = Instant::now();
686
687 let stats = localizer.get_cache_stats();
688 assert!(stats.is_cached);
689 assert_eq!(stats.cached_allocation_count, 3);
690 assert_eq!(stats.cached_ffi_count, 2);
691 assert_eq!(stats.cached_scope_count, 1);
692 }
693
694 #[test]
695 fn test_localized_export_data() {
696 let data = LocalizedExportData {
697 allocations: vec![],
698 enhanced_allocations: vec![],
699 stats: MemoryStats::default(),
700 ffi_stats: UnsafeFFIStats::default(),
701 scope_info: vec![],
702 timestamp: Instant::now(),
703 };
704
705 assert_eq!(data.total_allocation_count(), 0);
706 assert!(data.is_fresh(Duration::from_secs(1)));
707
708 let summary = data.get_summary();
709 assert!(summary.contains("allocations: 0"));
710 assert!(summary.contains("ffi_allocations: 0"));
711 assert!(summary.contains("scopes: 0"));
712 }
713
714 #[test]
715 fn test_localized_export_data_with_data() {
716 let data = LocalizedExportData {
717 allocations: vec![
718 AllocationInfo::new(0x1000, 256),
719 AllocationInfo::new(0x2000, 512),
720 ],
721 enhanced_allocations: vec![create_test_enhanced_allocation_info(0x3000, 128)],
722 stats: MemoryStats::default(),
723 ffi_stats: UnsafeFFIStats::default(),
724 scope_info: vec![
725 create_test_scope_info("scope1"),
726 create_test_scope_info("scope2"),
727 create_test_scope_info("scope3"),
728 ],
729 timestamp: Instant::now(),
730 };
731
732 assert_eq!(data.total_allocation_count(), 3); assert!(data.is_fresh(Duration::from_secs(1)));
734
735 let summary = data.get_summary();
736 assert!(summary.contains("allocations: 2"));
737 assert!(summary.contains("ffi_allocations: 1"));
738 assert!(summary.contains("scopes: 3"));
739 }
740
741 #[test]
742 fn test_localized_export_data_age() {
743 let old_timestamp = Instant::now() - Duration::from_secs(5);
744 let data = LocalizedExportData {
745 allocations: vec![],
746 enhanced_allocations: vec![],
747 stats: MemoryStats::default(),
748 ffi_stats: UnsafeFFIStats::default(),
749 scope_info: vec![],
750 timestamp: old_timestamp,
751 };
752
753 let age = data.age();
754 assert!(age >= Duration::from_secs(5));
755 assert!(!data.is_fresh(Duration::from_secs(1)));
756 assert!(data.is_fresh(Duration::from_secs(10)));
757 }
758
759 #[test]
760 fn test_data_gathering_stats_debug() {
761 let stats = DataGatheringStats {
762 total_time_ms: 150,
763 basic_data_time_ms: 80,
764 ffi_data_time_ms: 40,
765 scope_data_time_ms: 30,
766 allocation_count: 25,
767 ffi_allocation_count: 10,
768 scope_count: 5,
769 };
770
771 let debug_str = format!("{:?}", stats);
772 assert!(debug_str.contains("total_time_ms: 150"));
773 assert!(debug_str.contains("allocation_count: 25"));
774 assert!(debug_str.contains("ffi_allocation_count: 10"));
775 assert!(debug_str.contains("scope_count: 5"));
776 }
777
778 #[test]
779 fn test_cache_stats_debug() {
780 let stats = CacheStats {
781 is_cached: true,
782 cache_age_ms: 250,
783 cache_ttl_ms: 500,
784 cached_allocation_count: 15,
785 cached_ffi_count: 8,
786 cached_scope_count: 3,
787 };
788
789 let debug_str = format!("{:?}", stats);
790 assert!(debug_str.contains("is_cached: true"));
791 assert!(debug_str.contains("cache_age_ms: 250"));
792 assert!(debug_str.contains("cached_allocation_count: 15"));
793 }
794
795 #[test]
796 fn test_default_implementation() {
797 let localizer1 = DataLocalizer::default();
798 let localizer2 = DataLocalizer::new();
799
800 assert_eq!(localizer1.cache_ttl, localizer2.cache_ttl);
802 assert_eq!(localizer1.is_cache_valid(), localizer2.is_cache_valid());
803 }
804
805 #[test]
806 fn test_localized_export_data_clone() {
807 let original = LocalizedExportData {
808 allocations: vec![AllocationInfo::new(0x1000, 256)],
809 enhanced_allocations: vec![create_test_enhanced_allocation_info(0x2000, 128)],
810 stats: MemoryStats::default(),
811 ffi_stats: UnsafeFFIStats::default(),
812 scope_info: vec![create_test_scope_info("test_scope")],
813 timestamp: Instant::now(),
814 };
815
816 let cloned = original.clone();
817 assert_eq!(cloned.allocations.len(), original.allocations.len());
818 assert_eq!(
819 cloned.enhanced_allocations.len(),
820 original.enhanced_allocations.len()
821 );
822 assert_eq!(cloned.scope_info.len(), original.scope_info.len());
823 }
824
825 #[test]
826 fn test_data_gathering_stats_clone() {
827 let original = DataGatheringStats {
828 total_time_ms: 100,
829 basic_data_time_ms: 50,
830 ffi_data_time_ms: 30,
831 scope_data_time_ms: 20,
832 allocation_count: 10,
833 ffi_allocation_count: 5,
834 scope_count: 3,
835 };
836
837 let cloned = original.clone();
838 assert_eq!(cloned.total_time_ms, original.total_time_ms);
839 assert_eq!(cloned.allocation_count, original.allocation_count);
840 assert_eq!(cloned.ffi_allocation_count, original.ffi_allocation_count);
841 assert_eq!(cloned.scope_count, original.scope_count);
842 }
843
844 #[test]
845 fn test_cache_stats_clone() {
846 let original = CacheStats {
847 is_cached: true,
848 cache_age_ms: 100,
849 cache_ttl_ms: 200,
850 cached_allocation_count: 5,
851 cached_ffi_count: 3,
852 cached_scope_count: 2,
853 };
854
855 let cloned = original.clone();
856 assert_eq!(cloned.is_cached, original.is_cached);
857 assert_eq!(cloned.cache_age_ms, original.cache_age_ms);
858 assert_eq!(
859 cloned.cached_allocation_count,
860 original.cached_allocation_count
861 );
862 }
863}