1use crate::analysis::unsafe_ffi_tracker::MemoryPassport;
9use crate::core::types::{
10 AllocationInfo, BorrowInfo, CloneInfo, MemoryStats,
11 TrackingError::{ExportError, SerializationError},
12 TrackingResult,
13};
14use crate::UnsafeReport;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::path::Path;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct OwnershipEvent {
22 pub timestamp: u64,
24 pub event_type: String,
26 pub source_stack_id: u32,
28 pub details: HashMap<String, serde_json::Value>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LifetimeData {
35 pub allocation_ptr: usize,
37 pub ownership_history: Vec<OwnershipEvent>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct EnhancedAllocationInfo {
44 pub ptr: usize,
46 pub size: usize,
48 pub var_name: Option<String>,
50 pub type_name: Option<String>,
52 pub scope_name: Option<String>,
54 pub timestamp_alloc: u64,
56 pub timestamp_dealloc: Option<u64>,
58 pub borrow_count: usize,
60 pub stack_trace: Option<Vec<String>>,
62 pub is_leaked: bool,
64 pub lifetime_ms: Option<u64>,
66 pub borrow_info: Option<BorrowInfo>,
68 pub clone_info: Option<CloneInfo>,
70 pub ownership_history_available: bool,
72}
73
74pub struct EnhancedJsonExporter {
76 config: ExportConfig,
78}
79
80#[derive(Debug, Clone)]
82pub struct ExportConfig {
83 pub pretty_print: bool,
85 pub include_stack_traces: bool,
87 pub generate_lifetime_file: bool,
89 pub generate_unsafe_ffi_file: bool,
91 pub max_ownership_events: usize,
93}
94
95impl Default for ExportConfig {
96 fn default() -> Self {
97 Self {
98 pretty_print: true,
99 include_stack_traces: true,
100 generate_lifetime_file: true,
101 generate_unsafe_ffi_file: true,
102 max_ownership_events: 1000,
103 }
104 }
105}
106
107impl EnhancedJsonExporter {
108 pub fn new(config: ExportConfig) -> Self {
110 Self { config }
111 }
112
113 pub fn export_enhanced_analysis<P: AsRef<Path>>(
115 &self,
116 output_dir: P,
117 memory_stats: &MemoryStats,
118 unsafe_reports: &[UnsafeReport],
119 memory_passports: &[MemoryPassport],
120 ) -> TrackingResult<()> {
121 let output_dir = output_dir.as_ref();
122
123 tracing::info!(
124 "🚀 Starting enhanced JSON export to: {}",
125 output_dir.display()
126 );
127
128 std::fs::create_dir_all(output_dir)
130 .map_err(|e| ExportError(format!("Failed to create output directory: {e}",)))?;
131
132 self.export_memory_analysis(output_dir, memory_stats)?;
134
135 if self.config.generate_lifetime_file {
137 self.export_lifetime_data(output_dir, memory_stats)?;
138 }
139
140 if self.config.generate_unsafe_ffi_file {
142 self.export_unsafe_ffi_analysis(output_dir, unsafe_reports, memory_passports)?;
143 }
144
145 tracing::info!("✅ Enhanced JSON export completed successfully");
146 Ok(())
147 }
148
149 fn export_memory_analysis<P: AsRef<Path>>(
151 &self,
152 output_dir: P,
153 memory_stats: &MemoryStats,
154 ) -> TrackingResult<()> {
155 let output_path = output_dir.as_ref().join("memory_analysis.json");
156
157 tracing::info!("📊 Exporting memory analysis to: {}", output_path.display());
158
159 let enhanced_allocations: Vec<EnhancedAllocationInfo> = memory_stats
161 .allocations
162 .iter()
163 .map(|alloc| self.convert_to_enhanced_allocation(alloc))
164 .collect();
165
166 let analysis_data = serde_json::json!({
168 "metadata": {
169 "export_version": "2.0",
170 "export_timestamp": std::time::SystemTime::now()
171 .duration_since(std::time::UNIX_EPOCH)
172 .unwrap_or_default()
173 .as_secs(),
174 "specification": "improve.md compliant",
175 "total_allocations": enhanced_allocations.len(),
176 "extended_fields_included": true
177 },
178 "summary": {
179 "total_allocations": memory_stats.total_allocations,
180 "total_allocated": memory_stats.total_allocated,
181 "active_allocations": memory_stats.active_allocations,
182 "active_memory": memory_stats.active_memory,
183 "peak_allocations": memory_stats.peak_allocations,
184 "peak_memory": memory_stats.peak_memory,
185 "leaked_allocations": memory_stats.leaked_allocations,
186 "leaked_memory": memory_stats.leaked_memory
187 },
188 "allocations": enhanced_allocations
189 });
190
191 self.write_json_file(&output_path, &analysis_data)?;
193
194 tracing::info!(
195 "✅ Memory analysis exported: {} allocations",
196 enhanced_allocations.len()
197 );
198 Ok(())
199 }
200
201 fn export_lifetime_data<P: AsRef<Path>>(
203 &self,
204 output_dir: P,
205 memory_stats: &MemoryStats,
206 ) -> TrackingResult<()> {
207 let output_path = output_dir.as_ref().join("lifetime.json");
208
209 tracing::info!("🔄 Exporting lifetime data to: {}", output_path.display());
210
211 let lifetime_data: Vec<LifetimeData> = memory_stats
213 .allocations
214 .iter()
215 .filter(|alloc| alloc.ownership_history_available)
216 .map(|alloc| self.generate_lifetime_data(alloc))
217 .collect();
218
219 let lifetime_export = serde_json::json!({
220 "metadata": {
221 "export_version": "2.0",
222 "export_timestamp": std::time::SystemTime::now()
223 .duration_since(std::time::UNIX_EPOCH)
224 .unwrap_or_default()
225 .as_secs(),
226 "specification": "improve.md lifetime tracking",
227 "total_tracked_allocations": lifetime_data.len()
228 },
229 "ownership_histories": lifetime_data
230 });
231
232 self.write_json_file(&output_path, &lifetime_export)?;
233
234 tracing::info!(
235 "✅ Lifetime data exported: {} tracked allocations",
236 lifetime_data.len()
237 );
238 Ok(())
239 }
240
241 fn export_unsafe_ffi_analysis<P: AsRef<Path>>(
243 &self,
244 output_dir: P,
245 unsafe_reports: &[UnsafeReport],
246 memory_passports: &[MemoryPassport],
247 ) -> TrackingResult<()> {
248 let output_path = output_dir.as_ref().join("unsafe_ffi.json");
249
250 tracing::info!(
251 "🛡️ Exporting unsafe FFI analysis to: {}",
252 output_path.display()
253 );
254
255 let unsafe_ffi_export = serde_json::json!({
256 "metadata": {
257 "export_version": "2.0",
258 "export_timestamp": std::time::SystemTime::now()
259 .duration_since(std::time::UNIX_EPOCH)
260 .unwrap_or_default()
261 .as_secs(),
262 "specification": "improve.md unsafe FFI tracking",
263 "total_unsafe_reports": unsafe_reports.len(),
264 "total_memory_passports": memory_passports.len()
265 },
266 "unsafe_reports": unsafe_reports,
267 "memory_passports": memory_passports
268 });
269
270 self.write_json_file(&output_path, &unsafe_ffi_export)?;
271
272 tracing::info!(
273 "✅ Unsafe FFI analysis exported: {} reports, {} passports",
274 unsafe_reports.len(),
275 memory_passports.len()
276 );
277 Ok(())
278 }
279
280 fn convert_to_enhanced_allocation(&self, alloc: &AllocationInfo) -> EnhancedAllocationInfo {
282 EnhancedAllocationInfo {
283 ptr: alloc.ptr,
284 size: alloc.size,
285 var_name: alloc.var_name.clone(),
286 type_name: alloc.type_name.clone(),
287 scope_name: alloc.scope_name.clone(),
288 timestamp_alloc: alloc.timestamp_alloc,
289 timestamp_dealloc: alloc.timestamp_dealloc,
290 borrow_count: alloc.borrow_count,
291 stack_trace: if self.config.include_stack_traces {
292 alloc.stack_trace.clone()
293 } else {
294 None
295 },
296 is_leaked: alloc.is_leaked,
297 lifetime_ms: alloc.lifetime_ms,
298 borrow_info: alloc.borrow_info.clone(),
300 clone_info: alloc.clone_info.clone(),
301 ownership_history_available: alloc.ownership_history_available,
302 }
303 }
304
305 fn generate_lifetime_data(&self, alloc: &AllocationInfo) -> LifetimeData {
307 let mut ownership_history = Vec::new();
308
309 ownership_history.push(OwnershipEvent {
311 timestamp: alloc.timestamp_alloc,
312 event_type: "Allocated".to_string(),
313 source_stack_id: 1, details: HashMap::new(),
315 });
316
317 if let Some(clone_info) = &alloc.clone_info {
319 if clone_info.is_clone {
320 let mut clone_details = HashMap::new();
321 if let Some(original_ptr) = clone_info.original_ptr {
322 clone_details.insert(
323 "clone_source_ptr".to_string(),
324 serde_json::Value::Number(serde_json::Number::from(original_ptr)),
325 );
326 }
327
328 ownership_history.push(OwnershipEvent {
329 timestamp: alloc.timestamp_alloc + 1000, event_type: "Cloned".to_string(),
331 source_stack_id: 2,
332 details: clone_details,
333 });
334 }
335 }
336
337 if let Some(borrow_info) = &alloc.borrow_info {
339 for i in 0..borrow_info.immutable_borrows.min(5) {
340 let mut borrow_details = HashMap::new();
342 borrow_details.insert(
343 "borrower_scope".to_string(),
344 serde_json::Value::String(format!("scope_{i}")),
345 );
346
347 ownership_history.push(OwnershipEvent {
348 timestamp: alloc.timestamp_alloc + 2000 + (i as u64 * 1000),
349 event_type: "Borrowed".to_string(),
350 source_stack_id: 3 + i as u32,
351 details: borrow_details,
352 });
353 }
354 }
355
356 if let Some(dealloc_timestamp) = alloc.timestamp_dealloc {
358 ownership_history.push(OwnershipEvent {
359 timestamp: dealloc_timestamp,
360 event_type: "Dropped".to_string(),
361 source_stack_id: 99,
362 details: HashMap::new(),
363 });
364 }
365
366 ownership_history.truncate(self.config.max_ownership_events);
368
369 LifetimeData {
370 allocation_ptr: alloc.ptr,
371 ownership_history,
372 }
373 }
374
375 fn write_json_file<P: AsRef<Path>>(
377 &self,
378 path: P,
379 data: &serde_json::Value,
380 ) -> TrackingResult<()> {
381 let json_string = if self.config.pretty_print {
382 serde_json::to_string_pretty(data)
383 } else {
384 serde_json::to_string(data)
385 }
386 .map_err(|e| SerializationError(format!("Failed to serialize JSON: {e}",)))?;
387
388 std::fs::write(&path, json_string).map_err(|e| {
389 ExportError(format!(
390 "Failed to write file {}: {}",
391 path.as_ref().display(),
392 e
393 ))
394 })?;
395
396 Ok(())
397 }
398}
399
400impl Default for EnhancedJsonExporter {
401 fn default() -> Self {
402 Self::new(ExportConfig::default())
403 }
404}
405
406pub fn export_enhanced_json<P: AsRef<Path>>(
408 output_dir: P,
409 memory_stats: &MemoryStats,
410 unsafe_reports: &[UnsafeReport],
411 memory_passports: &[MemoryPassport],
412) -> TrackingResult<()> {
413 let exporter = EnhancedJsonExporter::default();
414 exporter.export_enhanced_analysis(output_dir, memory_stats, unsafe_reports, memory_passports)
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use crate::core::types::{AllocationInfo, BorrowInfo, CloneInfo};
421
422 #[test]
423 fn test_enhanced_allocation_conversion() {
424 let exporter = EnhancedJsonExporter::default();
425
426 let mut alloc = AllocationInfo::new(0x1000, 64);
427 alloc.borrow_info = Some(BorrowInfo {
428 immutable_borrows: 5,
429 mutable_borrows: 2,
430 max_concurrent_borrows: 3,
431 last_borrow_timestamp: Some(1234567890),
432 });
433 alloc.clone_info = Some(CloneInfo {
434 clone_count: 2,
435 is_clone: true,
436 original_ptr: Some(0x2000),
437 });
438 alloc.ownership_history_available = true;
439
440 let enhanced = exporter.convert_to_enhanced_allocation(&alloc);
441
442 assert_eq!(enhanced.ptr, 0x1000);
443 assert_eq!(enhanced.size, 64);
444 assert!(enhanced.borrow_info.is_some());
445 assert!(enhanced.clone_info.is_some());
446 assert!(enhanced.ownership_history_available);
447 }
448
449 #[test]
450 fn test_lifetime_data_generation() {
451 let exporter = EnhancedJsonExporter::default();
452
453 let mut alloc = AllocationInfo::new(0x1000, 64);
454 alloc.borrow_info = Some(BorrowInfo {
455 immutable_borrows: 2,
456 mutable_borrows: 1,
457 max_concurrent_borrows: 2,
458 last_borrow_timestamp: Some(1234567890),
459 });
460 alloc.ownership_history_available = true;
461
462 let lifetime_data = exporter.generate_lifetime_data(&alloc);
463
464 assert_eq!(lifetime_data.allocation_ptr, 0x1000);
465 assert!(!lifetime_data.ownership_history.is_empty());
466
467 let allocated_event = lifetime_data
469 .ownership_history
470 .iter()
471 .find(|e| e.event_type == "Allocated");
472 assert!(allocated_event.is_some());
473 }
474
475 fn create_test_allocation(ptr: usize, size: usize) -> AllocationInfo {
476 let mut alloc = AllocationInfo::new(ptr, size);
477 alloc.var_name = Some("test_var".to_string());
478 alloc.type_name = Some("TestType".to_string());
479 alloc.scope_name = Some("test_scope".to_string());
480 alloc.timestamp_alloc = 1234567890;
481 alloc.timestamp_dealloc = None;
482 alloc.borrow_count = 0;
483 alloc.is_leaked = false;
484 alloc.lifetime_ms = Some(1000);
485 alloc
486 }
487
488 fn create_test_memory_stats() -> MemoryStats {
489 let allocations = vec![
490 create_test_allocation(0x1000, 64),
491 create_test_allocation(0x2000, 128),
492 ];
493
494 MemoryStats {
495 total_allocations: 2,
496 total_allocated: 192,
497 active_allocations: 2,
498 active_memory: 192,
499 peak_allocations: 2,
500 peak_memory: 192,
501 total_deallocations: 0,
502 total_deallocated: 0,
503 leaked_allocations: 0,
504 leaked_memory: 0,
505 fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
506 lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
507 allocations,
508 system_library_stats: crate::core::types::SystemLibraryStats::default(),
509 concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
510 }
511 }
512
513 #[test]
514 fn test_export_config_default() {
515 let config = ExportConfig::default();
516
517 assert!(config.pretty_print);
518 assert!(config.include_stack_traces);
519 assert!(config.generate_lifetime_file);
520 assert!(config.generate_unsafe_ffi_file);
521 assert_eq!(config.max_ownership_events, 1000);
522 }
523
524 #[test]
525 fn test_export_config_debug_clone() {
526 let config = ExportConfig {
527 pretty_print: false,
528 include_stack_traces: false,
529 generate_lifetime_file: false,
530 generate_unsafe_ffi_file: false,
531 max_ownership_events: 500,
532 };
533
534 let debug_str = format!("{:?}", config);
536 assert!(debug_str.contains("ExportConfig"));
537 assert!(debug_str.contains("pretty_print"));
538 assert!(debug_str.contains("false"));
539
540 let cloned_config = config.clone();
542 assert_eq!(cloned_config.pretty_print, config.pretty_print);
543 assert_eq!(
544 cloned_config.include_stack_traces,
545 config.include_stack_traces
546 );
547 assert_eq!(
548 cloned_config.generate_lifetime_file,
549 config.generate_lifetime_file
550 );
551 assert_eq!(
552 cloned_config.generate_unsafe_ffi_file,
553 config.generate_unsafe_ffi_file
554 );
555 assert_eq!(
556 cloned_config.max_ownership_events,
557 config.max_ownership_events
558 );
559 }
560
561 #[test]
562 fn test_enhanced_json_exporter_new() {
563 let config = ExportConfig {
564 pretty_print: false,
565 include_stack_traces: true,
566 generate_lifetime_file: false,
567 generate_unsafe_ffi_file: true,
568 max_ownership_events: 2000,
569 };
570
571 let exporter = EnhancedJsonExporter::new(config.clone());
572 assert_eq!(exporter.config.pretty_print, config.pretty_print);
573 assert_eq!(
574 exporter.config.include_stack_traces,
575 config.include_stack_traces
576 );
577 assert_eq!(
578 exporter.config.generate_lifetime_file,
579 config.generate_lifetime_file
580 );
581 assert_eq!(
582 exporter.config.generate_unsafe_ffi_file,
583 config.generate_unsafe_ffi_file
584 );
585 assert_eq!(
586 exporter.config.max_ownership_events,
587 config.max_ownership_events
588 );
589 }
590
591 #[test]
592 fn test_enhanced_json_exporter_default() {
593 let exporter1 = EnhancedJsonExporter::default();
594 let exporter2 = EnhancedJsonExporter::new(ExportConfig::default());
595
596 assert_eq!(exporter1.config.pretty_print, exporter2.config.pretty_print);
597 assert_eq!(
598 exporter1.config.include_stack_traces,
599 exporter2.config.include_stack_traces
600 );
601 assert_eq!(
602 exporter1.config.generate_lifetime_file,
603 exporter2.config.generate_lifetime_file
604 );
605 assert_eq!(
606 exporter1.config.generate_unsafe_ffi_file,
607 exporter2.config.generate_unsafe_ffi_file
608 );
609 assert_eq!(
610 exporter1.config.max_ownership_events,
611 exporter2.config.max_ownership_events
612 );
613 }
614
615 #[test]
616 fn test_convert_to_enhanced_allocation_with_stack_traces() {
617 let config = ExportConfig {
618 include_stack_traces: true,
619 ..Default::default()
620 };
621 let exporter = EnhancedJsonExporter::new(config);
622
623 let mut alloc = create_test_allocation(0x1000, 64);
624 alloc.stack_trace = Some(vec!["main".to_string(), "allocate".to_string()]);
625 alloc.borrow_info = Some(BorrowInfo {
626 immutable_borrows: 3,
627 mutable_borrows: 1,
628 max_concurrent_borrows: 2,
629 last_borrow_timestamp: Some(1234567890),
630 });
631
632 let enhanced = exporter.convert_to_enhanced_allocation(&alloc);
633
634 assert_eq!(enhanced.ptr, 0x1000);
635 assert_eq!(enhanced.size, 64);
636 assert!(enhanced.stack_trace.is_some());
637 assert_eq!(enhanced.stack_trace.as_ref().unwrap().len(), 2);
638 assert!(enhanced.borrow_info.is_some());
639 assert_eq!(enhanced.borrow_info.as_ref().unwrap().immutable_borrows, 3);
640 }
641
642 #[test]
643 fn test_convert_to_enhanced_allocation_without_stack_traces() {
644 let config = ExportConfig {
645 include_stack_traces: false,
646 ..Default::default()
647 };
648 let exporter = EnhancedJsonExporter::new(config);
649
650 let mut alloc = create_test_allocation(0x1000, 64);
651 alloc.stack_trace = Some(vec!["main".to_string(), "allocate".to_string()]);
652
653 let enhanced = exporter.convert_to_enhanced_allocation(&alloc);
654
655 assert_eq!(enhanced.ptr, 0x1000);
656 assert_eq!(enhanced.size, 64);
657 assert!(enhanced.stack_trace.is_none()); }
659
660 #[test]
661 fn test_generate_lifetime_data_with_clone_info() {
662 let exporter = EnhancedJsonExporter::default();
663
664 let mut alloc = create_test_allocation(0x1000, 64);
665 alloc.clone_info = Some(CloneInfo {
666 clone_count: 2,
667 is_clone: true,
668 original_ptr: Some(0x2000),
669 });
670 alloc.ownership_history_available = true;
671
672 let lifetime_data = exporter.generate_lifetime_data(&alloc);
673
674 assert_eq!(lifetime_data.allocation_ptr, 0x1000);
675 assert!(!lifetime_data.ownership_history.is_empty());
676
677 let allocated_event = lifetime_data
679 .ownership_history
680 .iter()
681 .find(|e| e.event_type == "Allocated");
682 assert!(allocated_event.is_some());
683
684 let cloned_event = lifetime_data
685 .ownership_history
686 .iter()
687 .find(|e| e.event_type == "Cloned");
688 assert!(cloned_event.is_some());
689
690 let clone_event = cloned_event.unwrap();
692 assert!(clone_event.details.contains_key("clone_source_ptr"));
693 }
694
695 #[test]
696 fn test_generate_lifetime_data_with_borrow_events() {
697 let exporter = EnhancedJsonExporter::default();
698
699 let mut alloc = create_test_allocation(0x1000, 64);
700 alloc.borrow_info = Some(BorrowInfo {
701 immutable_borrows: 3,
702 mutable_borrows: 1,
703 max_concurrent_borrows: 2,
704 last_borrow_timestamp: Some(1234567890),
705 });
706 alloc.ownership_history_available = true;
707
708 let lifetime_data = exporter.generate_lifetime_data(&alloc);
709
710 let borrowed_events: Vec<_> = lifetime_data
712 .ownership_history
713 .iter()
714 .filter(|e| e.event_type == "Borrowed")
715 .collect();
716 assert_eq!(borrowed_events.len(), 3); for borrow_event in borrowed_events {
720 assert!(borrow_event.details.contains_key("borrower_scope"));
721 }
722 }
723
724 #[test]
725 fn test_generate_lifetime_data_with_deallocation() {
726 let exporter = EnhancedJsonExporter::default();
727
728 let mut alloc = create_test_allocation(0x1000, 64);
729 alloc.timestamp_dealloc = Some(1234567890 + 5000);
730 alloc.ownership_history_available = true;
731
732 let lifetime_data = exporter.generate_lifetime_data(&alloc);
733
734 let dropped_event = lifetime_data
736 .ownership_history
737 .iter()
738 .find(|e| e.event_type == "Dropped");
739 assert!(dropped_event.is_some());
740
741 let drop_event = dropped_event.unwrap();
742 assert_eq!(drop_event.timestamp, 1234567890 + 5000);
743 assert_eq!(drop_event.source_stack_id, 99);
744 }
745
746 #[test]
747 fn test_generate_lifetime_data_with_max_events_limit() {
748 let config = ExportConfig {
749 max_ownership_events: 2,
750 ..Default::default()
751 };
752 let exporter = EnhancedJsonExporter::new(config);
753
754 let mut alloc = create_test_allocation(0x1000, 64);
755 alloc.borrow_info = Some(BorrowInfo {
756 immutable_borrows: 10, mutable_borrows: 1,
758 max_concurrent_borrows: 5,
759 last_borrow_timestamp: Some(1234567890),
760 });
761 alloc.timestamp_dealloc = Some(1234567890 + 5000);
762 alloc.ownership_history_available = true;
763
764 let lifetime_data = exporter.generate_lifetime_data(&alloc);
765
766 assert!(lifetime_data.ownership_history.len() <= 2);
768 }
769
770 #[test]
771 fn test_ownership_event_debug_clone_serialize() {
772 let mut details = HashMap::new();
773 details.insert(
774 "test_key".to_string(),
775 serde_json::Value::String("test_value".to_string()),
776 );
777
778 let event = OwnershipEvent {
779 timestamp: 1234567890,
780 event_type: "TestEvent".to_string(),
781 source_stack_id: 42,
782 details,
783 };
784
785 let debug_str = format!("{:?}", event);
787 assert!(debug_str.contains("OwnershipEvent"));
788 assert!(debug_str.contains("TestEvent"));
789 assert!(debug_str.contains("42"));
790
791 let cloned_event = event.clone();
793 assert_eq!(cloned_event.timestamp, event.timestamp);
794 assert_eq!(cloned_event.event_type, event.event_type);
795 assert_eq!(cloned_event.source_stack_id, event.source_stack_id);
796 assert_eq!(cloned_event.details.len(), event.details.len());
797
798 let serialized = serde_json::to_string(&event);
800 assert!(serialized.is_ok());
801 let json_str = serialized.unwrap();
802 assert!(json_str.contains("TestEvent"));
803 assert!(json_str.contains("1234567890"));
804
805 let deserialized: Result<OwnershipEvent, _> = serde_json::from_str(&json_str);
807 assert!(deserialized.is_ok());
808 let deserialized_event = deserialized.unwrap();
809 assert_eq!(deserialized_event.timestamp, event.timestamp);
810 assert_eq!(deserialized_event.event_type, event.event_type);
811 }
812
813 #[test]
814 fn test_lifetime_data_debug_clone_serialize() {
815 let ownership_history = vec![
816 OwnershipEvent {
817 timestamp: 1234567890,
818 event_type: "Allocated".to_string(),
819 source_stack_id: 1,
820 details: HashMap::new(),
821 },
822 OwnershipEvent {
823 timestamp: 1234567900,
824 event_type: "Dropped".to_string(),
825 source_stack_id: 2,
826 details: HashMap::new(),
827 },
828 ];
829
830 let lifetime_data = LifetimeData {
831 allocation_ptr: 0x1000,
832 ownership_history,
833 };
834
835 let debug_str = format!("{:?}", lifetime_data);
837 assert!(debug_str.contains("LifetimeData"));
838 assert!(debug_str.contains("4096")); assert!(debug_str.contains("Allocated"));
840
841 let cloned_data = lifetime_data.clone();
843 assert_eq!(cloned_data.allocation_ptr, lifetime_data.allocation_ptr);
844 assert_eq!(
845 cloned_data.ownership_history.len(),
846 lifetime_data.ownership_history.len()
847 );
848
849 let serialized = serde_json::to_string(&lifetime_data);
851 assert!(serialized.is_ok());
852 let json_str = serialized.unwrap();
853 assert!(json_str.contains("allocation_ptr"));
854 assert!(json_str.contains("ownership_history"));
855
856 let deserialized: Result<LifetimeData, _> = serde_json::from_str(&json_str);
858 assert!(deserialized.is_ok());
859 let deserialized_data = deserialized.unwrap();
860 assert_eq!(
861 deserialized_data.allocation_ptr,
862 lifetime_data.allocation_ptr
863 );
864 assert_eq!(
865 deserialized_data.ownership_history.len(),
866 lifetime_data.ownership_history.len()
867 );
868 }
869
870 #[test]
871 fn test_enhanced_allocation_info_debug_clone_serialize() {
872 let enhanced_alloc = EnhancedAllocationInfo {
873 ptr: 0x1000,
874 size: 64,
875 var_name: Some("test_var".to_string()),
876 type_name: Some("TestType".to_string()),
877 scope_name: Some("test_scope".to_string()),
878 timestamp_alloc: 1234567890,
879 timestamp_dealloc: Some(1234567900),
880 borrow_count: 2,
881 stack_trace: Some(vec!["main".to_string()]),
882 is_leaked: false,
883 lifetime_ms: Some(1000),
884 borrow_info: Some(BorrowInfo {
885 immutable_borrows: 1,
886 mutable_borrows: 0,
887 max_concurrent_borrows: 1,
888 last_borrow_timestamp: Some(1234567890),
889 }),
890 clone_info: None,
891 ownership_history_available: true,
892 };
893
894 let debug_str = format!("{:?}", enhanced_alloc);
896 assert!(debug_str.contains("EnhancedAllocationInfo"));
897 assert!(debug_str.contains("test_var"));
898 assert!(debug_str.contains("TestType"));
899
900 let cloned_alloc = enhanced_alloc.clone();
902 assert_eq!(cloned_alloc.ptr, enhanced_alloc.ptr);
903 assert_eq!(cloned_alloc.size, enhanced_alloc.size);
904 assert_eq!(cloned_alloc.var_name, enhanced_alloc.var_name);
905 assert_eq!(
906 cloned_alloc.ownership_history_available,
907 enhanced_alloc.ownership_history_available
908 );
909
910 let serialized = serde_json::to_string(&enhanced_alloc);
912 assert!(serialized.is_ok());
913 let json_str = serialized.unwrap();
914 assert!(json_str.contains("test_var"));
915 assert!(json_str.contains("TestType"));
916 assert!(json_str.contains("ownership_history_available"));
917
918 let deserialized: Result<EnhancedAllocationInfo, _> = serde_json::from_str(&json_str);
920 assert!(deserialized.is_ok());
921 let deserialized_alloc = deserialized.unwrap();
922 assert_eq!(deserialized_alloc.ptr, enhanced_alloc.ptr);
923 assert_eq!(deserialized_alloc.var_name, enhanced_alloc.var_name);
924 assert_eq!(
925 deserialized_alloc.ownership_history_available,
926 enhanced_alloc.ownership_history_available
927 );
928 }
929
930 #[test]
931 fn test_export_memory_analysis() -> TrackingResult<()> {
932 use tempfile::TempDir;
933
934 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
935 let exporter = EnhancedJsonExporter::default();
936 let memory_stats = create_test_memory_stats();
937
938 exporter.export_memory_analysis(&temp_dir, &memory_stats)?;
939
940 let output_path = temp_dir.path().join("memory_analysis.json");
941 assert!(output_path.exists());
942
943 let content =
945 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
946 let json_data: serde_json::Value =
947 serde_json::from_str(&content).map_err(|e| SerializationError(e.to_string()))?;
948
949 assert!(json_data.get("metadata").is_some());
950 assert!(json_data.get("summary").is_some());
951 assert!(json_data.get("allocations").is_some());
952
953 let metadata = &json_data["metadata"];
954 assert_eq!(metadata["export_version"].as_str().unwrap(), "2.0");
955 assert_eq!(
956 metadata["specification"].as_str().unwrap(),
957 "improve.md compliant"
958 );
959 assert_eq!(metadata["total_allocations"].as_u64().unwrap(), 2);
960
961 Ok(())
962 }
963
964 #[test]
965 fn test_export_lifetime_data() -> TrackingResult<()> {
966 use tempfile::TempDir;
967
968 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
969 let exporter = EnhancedJsonExporter::default();
970
971 let mut memory_stats = create_test_memory_stats();
972 memory_stats.allocations[0].ownership_history_available = true;
974 memory_stats.allocations[1].ownership_history_available = true;
975
976 exporter.export_lifetime_data(&temp_dir, &memory_stats)?;
977
978 let output_path = temp_dir.path().join("lifetime.json");
979 assert!(output_path.exists());
980
981 let content =
983 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
984 let json_data: serde_json::Value =
985 serde_json::from_str(&content).map_err(|e| SerializationError(e.to_string()))?;
986
987 assert!(json_data.get("metadata").is_some());
988 assert!(json_data.get("ownership_histories").is_some());
989
990 let metadata = &json_data["metadata"];
991 assert_eq!(metadata["export_version"].as_str().unwrap(), "2.0");
992 assert_eq!(
993 metadata["specification"].as_str().unwrap(),
994 "improve.md lifetime tracking"
995 );
996 assert_eq!(metadata["total_tracked_allocations"].as_u64().unwrap(), 2);
997
998 Ok(())
999 }
1000
1001 #[test]
1002 fn test_export_unsafe_ffi_analysis() -> TrackingResult<()> {
1003 use tempfile::TempDir;
1004
1005 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1006 let exporter = EnhancedJsonExporter::default();
1007
1008 let unsafe_reports = vec![];
1009 let memory_passports = vec![];
1010
1011 exporter.export_unsafe_ffi_analysis(&temp_dir, &unsafe_reports, &memory_passports)?;
1012
1013 let output_path = temp_dir.path().join("unsafe_ffi.json");
1014 assert!(output_path.exists());
1015
1016 let content =
1018 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
1019 let json_data: serde_json::Value =
1020 serde_json::from_str(&content).map_err(|e| SerializationError(e.to_string()))?;
1021
1022 assert!(json_data.get("metadata").is_some());
1023 assert!(json_data.get("unsafe_reports").is_some());
1024 assert!(json_data.get("memory_passports").is_some());
1025
1026 let metadata = &json_data["metadata"];
1027 assert_eq!(metadata["export_version"].as_str().unwrap(), "2.0");
1028 assert_eq!(
1029 metadata["specification"].as_str().unwrap(),
1030 "improve.md unsafe FFI tracking"
1031 );
1032 assert_eq!(metadata["total_unsafe_reports"].as_u64().unwrap(), 0);
1033 assert_eq!(metadata["total_memory_passports"].as_u64().unwrap(), 0);
1034
1035 Ok(())
1036 }
1037
1038 #[test]
1039 fn test_export_enhanced_analysis_all_files() -> TrackingResult<()> {
1040 use tempfile::TempDir;
1041
1042 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1043 let exporter = EnhancedJsonExporter::default();
1044 let memory_stats = create_test_memory_stats();
1045 let unsafe_reports = vec![];
1046 let memory_passports = vec![];
1047
1048 exporter.export_enhanced_analysis(
1049 &temp_dir,
1050 &memory_stats,
1051 &unsafe_reports,
1052 &memory_passports,
1053 )?;
1054
1055 assert!(temp_dir.path().join("memory_analysis.json").exists());
1057 assert!(temp_dir.path().join("lifetime.json").exists());
1058 assert!(temp_dir.path().join("unsafe_ffi.json").exists());
1059
1060 Ok(())
1061 }
1062
1063 #[test]
1064 fn test_export_enhanced_analysis_selective_files() -> TrackingResult<()> {
1065 use tempfile::TempDir;
1066
1067 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1068 let config = ExportConfig {
1069 generate_lifetime_file: false,
1070 generate_unsafe_ffi_file: true,
1071 ..Default::default()
1072 };
1073 let exporter = EnhancedJsonExporter::new(config);
1074 let memory_stats = create_test_memory_stats();
1075 let unsafe_reports = vec![];
1076 let memory_passports = vec![];
1077
1078 exporter.export_enhanced_analysis(
1079 &temp_dir,
1080 &memory_stats,
1081 &unsafe_reports,
1082 &memory_passports,
1083 )?;
1084
1085 assert!(temp_dir.path().join("memory_analysis.json").exists());
1087 assert!(!temp_dir.path().join("lifetime.json").exists()); assert!(temp_dir.path().join("unsafe_ffi.json").exists());
1089
1090 Ok(())
1091 }
1092
1093 #[test]
1094 fn test_write_json_file_pretty_print() -> TrackingResult<()> {
1095 use tempfile::TempDir;
1096
1097 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1098 let config = ExportConfig {
1099 pretty_print: true,
1100 ..Default::default()
1101 };
1102 let exporter = EnhancedJsonExporter::new(config);
1103
1104 let test_data = serde_json::json!({
1105 "test": "value",
1106 "number": 42
1107 });
1108
1109 let output_path = temp_dir.path().join("pretty_test.json");
1110 exporter.write_json_file(&output_path, &test_data)?;
1111
1112 assert!(output_path.exists());
1113
1114 let content =
1115 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
1116
1117 assert!(content.contains('\n'));
1119 assert!(content.contains(" ")); Ok(())
1122 }
1123
1124 #[test]
1125 fn test_write_json_file_compact() -> TrackingResult<()> {
1126 use tempfile::TempDir;
1127
1128 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1129 let config = ExportConfig {
1130 pretty_print: false,
1131 ..Default::default()
1132 };
1133 let exporter = EnhancedJsonExporter::new(config);
1134
1135 let test_data = serde_json::json!({
1136 "test": "value",
1137 "number": 42
1138 });
1139
1140 let output_path = temp_dir.path().join("compact_test.json");
1141 exporter.write_json_file(&output_path, &test_data)?;
1142
1143 assert!(output_path.exists());
1144
1145 let content =
1146 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
1147
1148 assert!(!content.contains('\n'));
1150 assert!(!content.contains(" ")); Ok(())
1153 }
1154
1155 #[test]
1156 fn test_export_enhanced_json_convenience_function() -> TrackingResult<()> {
1157 use tempfile::TempDir;
1158
1159 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1160 let memory_stats = create_test_memory_stats();
1161 let unsafe_reports = vec![];
1162 let memory_passports = vec![];
1163
1164 export_enhanced_json(&temp_dir, &memory_stats, &unsafe_reports, &memory_passports)?;
1165
1166 assert!(temp_dir.path().join("memory_analysis.json").exists());
1168 assert!(temp_dir.path().join("lifetime.json").exists());
1169 assert!(temp_dir.path().join("unsafe_ffi.json").exists());
1170
1171 Ok(())
1172 }
1173
1174 #[test]
1175 fn test_export_with_empty_allocations() -> TrackingResult<()> {
1176 use tempfile::TempDir;
1177
1178 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1179 let exporter = EnhancedJsonExporter::default();
1180
1181 let empty_memory_stats = MemoryStats {
1182 total_allocations: 0,
1183 total_allocated: 0,
1184 active_allocations: 0,
1185 active_memory: 0,
1186 peak_allocations: 0,
1187 peak_memory: 0,
1188 total_deallocations: 0,
1189 total_deallocated: 0,
1190 leaked_allocations: 0,
1191 leaked_memory: 0,
1192 fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
1193 lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
1194 allocations: vec![], system_library_stats: crate::core::types::SystemLibraryStats::default(),
1196 concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
1197 };
1198
1199 exporter.export_memory_analysis(&temp_dir, &empty_memory_stats)?;
1200
1201 let output_path = temp_dir.path().join("memory_analysis.json");
1202 assert!(output_path.exists());
1203
1204 let content =
1206 std::fs::read_to_string(&output_path).map_err(|e| ExportError(e.to_string()))?;
1207 let json_data: serde_json::Value =
1208 serde_json::from_str(&content).map_err(|e| SerializationError(e.to_string()))?;
1209
1210 let metadata = &json_data["metadata"];
1211 assert_eq!(metadata["total_allocations"].as_u64().unwrap(), 0);
1212
1213 let allocations = json_data["allocations"].as_array().unwrap();
1214 assert_eq!(allocations.len(), 0);
1215
1216 Ok(())
1217 }
1218
1219 #[test]
1220 fn test_export_with_large_dataset() -> TrackingResult<()> {
1221 use tempfile::TempDir;
1222
1223 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1224 let exporter = EnhancedJsonExporter::default();
1225
1226 let mut large_allocations = Vec::new();
1228 for i in 0..1000 {
1229 let mut alloc = create_test_allocation(0x1000 + i * 0x100, 64 + i % 100);
1230 alloc.ownership_history_available = i % 2 == 0; large_allocations.push(alloc);
1232 }
1233
1234 let large_memory_stats = MemoryStats {
1235 total_allocations: 1000,
1236 total_allocated: 114000, active_allocations: 1000,
1238 active_memory: 114000,
1239 peak_allocations: 1000,
1240 peak_memory: 114000,
1241 total_deallocations: 0,
1242 total_deallocated: 0,
1243 leaked_allocations: 0,
1244 leaked_memory: 0,
1245 fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
1246 lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
1247 allocations: large_allocations,
1248 system_library_stats: crate::core::types::SystemLibraryStats::default(),
1249 concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
1250 };
1251
1252 let unsafe_reports = vec![];
1253 let memory_passports = vec![];
1254
1255 exporter.export_enhanced_analysis(
1256 &temp_dir,
1257 &large_memory_stats,
1258 &unsafe_reports,
1259 &memory_passports,
1260 )?;
1261
1262 assert!(temp_dir.path().join("memory_analysis.json").exists());
1264 assert!(temp_dir.path().join("lifetime.json").exists());
1265 assert!(temp_dir.path().join("unsafe_ffi.json").exists());
1266
1267 let memory_content = std::fs::read_to_string(temp_dir.path().join("memory_analysis.json"))
1269 .map_err(|e| ExportError(e.to_string()))?;
1270 let memory_json: serde_json::Value =
1271 serde_json::from_str(&memory_content).map_err(|e| SerializationError(e.to_string()))?;
1272
1273 assert_eq!(
1274 memory_json["metadata"]["total_allocations"]
1275 .as_u64()
1276 .unwrap(),
1277 1000
1278 );
1279
1280 let lifetime_content = std::fs::read_to_string(temp_dir.path().join("lifetime.json"))
1282 .map_err(|e| ExportError(e.to_string()))?;
1283 let lifetime_json: serde_json::Value = serde_json::from_str(&lifetime_content)
1284 .map_err(|e| SerializationError(e.to_string()))?;
1285
1286 assert_eq!(
1287 lifetime_json["metadata"]["total_tracked_allocations"]
1288 .as_u64()
1289 .unwrap(),
1290 500
1291 ); Ok(())
1294 }
1295
1296 #[test]
1297 fn test_export_with_complex_allocations() -> TrackingResult<()> {
1298 use tempfile::TempDir;
1299
1300 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1301 let exporter = EnhancedJsonExporter::default();
1302
1303 let mut complex_allocations = Vec::new();
1305
1306 let mut alloc1 = create_test_allocation(0x1000, 1024);
1308 alloc1.ownership_history_available = true;
1309 alloc1.borrow_count = 5;
1310 alloc1.is_leaked = false;
1311 alloc1.var_name = Some("complex_vector".to_string());
1312 alloc1.type_name = Some("Vec<String>".to_string());
1313 complex_allocations.push(alloc1);
1314
1315 let mut alloc2 = create_test_allocation(0x2000, 512);
1317 alloc2.is_leaked = true;
1318 alloc2.var_name = Some("leaked_data".to_string());
1319 alloc2.type_name = Some("Box<[u8]>".to_string());
1320 complex_allocations.push(alloc2);
1321
1322 let mut alloc3 = create_test_allocation(0x3000, 256);
1324 alloc3.borrow_count = 100;
1325 alloc3.var_name = Some("shared_resource".to_string());
1326 alloc3.type_name = Some("Rc<RefCell<Data>>".to_string());
1327 complex_allocations.push(alloc3);
1328
1329 let memory_stats = MemoryStats {
1330 total_allocations: 3,
1331 total_allocated: 1792,
1332 active_allocations: 3,
1333 active_memory: 1792,
1334 peak_allocations: 3,
1335 peak_memory: 1792,
1336 total_deallocations: 0,
1337 total_deallocated: 0,
1338 leaked_allocations: 1,
1339 leaked_memory: 512,
1340 fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
1341 lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
1342 allocations: complex_allocations,
1343 system_library_stats: crate::core::types::SystemLibraryStats::default(),
1344 concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
1345 };
1346
1347 let unsafe_reports = vec![];
1348 let memory_passports = vec![];
1349
1350 exporter.export_enhanced_analysis(
1351 &temp_dir,
1352 &memory_stats,
1353 &unsafe_reports,
1354 &memory_passports,
1355 )?;
1356
1357 let memory_content = std::fs::read_to_string(temp_dir.path().join("memory_analysis.json"))
1359 .map_err(|e| ExportError(e.to_string()))?;
1360 let memory_json: serde_json::Value =
1361 serde_json::from_str(&memory_content).map_err(|e| SerializationError(e.to_string()))?;
1362
1363 let allocations = memory_json["allocations"].as_array().unwrap();
1364 assert_eq!(allocations.len(), 3);
1365
1366 let leaked_alloc = allocations
1368 .iter()
1369 .find(|a| a["is_leaked"].as_bool().unwrap_or(false));
1370 assert!(leaked_alloc.is_some());
1371 assert_eq!(leaked_alloc.unwrap()["size"].as_u64().unwrap(), 512);
1372
1373 let high_borrow = allocations
1375 .iter()
1376 .find(|a| a["borrow_count"].as_u64().unwrap_or(0) == 100);
1377 assert!(high_borrow.is_some());
1378
1379 Ok(())
1380 }
1381
1382 #[test]
1383 fn test_export_with_unsafe_reports() -> TrackingResult<()> {
1384 use tempfile::TempDir;
1385
1386 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1387 let exporter = EnhancedJsonExporter::default();
1388
1389 let allocations = vec![create_test_allocation(0x1000, 64)];
1390 let mut memory_stats = create_test_memory_stats();
1391 memory_stats.allocations = allocations;
1392
1393 let unsafe_reports = vec![];
1395
1396 let memory_passports = vec![];
1397
1398 exporter.export_enhanced_analysis(
1399 &temp_dir,
1400 &memory_stats,
1401 &unsafe_reports,
1402 &memory_passports,
1403 )?;
1404
1405 let unsafe_content = std::fs::read_to_string(temp_dir.path().join("unsafe_ffi.json"))
1407 .map_err(|e| ExportError(e.to_string()))?;
1408 let unsafe_json: serde_json::Value =
1409 serde_json::from_str(&unsafe_content).map_err(|e| SerializationError(e.to_string()))?;
1410
1411 let reports = unsafe_json["unsafe_reports"].as_array().unwrap();
1412 assert_eq!(reports.len(), 0); Ok(())
1415 }
1416
1417 #[test]
1418 fn test_export_error_handling() -> TrackingResult<()> {
1419 let exporter = EnhancedJsonExporter::default();
1420
1421 let invalid_path = if cfg!(windows) {
1423 std::path::Path::new("C:\\invalid<>|path") } else {
1425 std::path::Path::new("/dev/null/invalid") };
1427
1428 let allocations = vec![create_test_allocation(0x1000, 64)];
1429 let mut memory_stats = create_test_memory_stats();
1430 memory_stats.allocations = allocations;
1431 let unsafe_reports = vec![];
1432 let memory_passports = vec![];
1433
1434 let result = exporter.export_enhanced_analysis(
1435 invalid_path,
1436 &memory_stats,
1437 &unsafe_reports,
1438 &memory_passports,
1439 );
1440
1441 assert!(
1442 result.is_err(),
1443 "Expected error when exporting to invalid path: {:?}",
1444 invalid_path
1445 );
1446
1447 Ok(())
1448 }
1449
1450 #[test]
1451 fn test_json_serialization_edge_cases() -> TrackingResult<()> {
1452 use tempfile::TempDir;
1453
1454 let temp_dir = TempDir::new().map_err(|e| ExportError(e.to_string()))?;
1455 let exporter = EnhancedJsonExporter::default();
1456
1457 let mut edge_allocations = Vec::new();
1459
1460 let mut alloc1 = create_test_allocation(0x1000, 0);
1462 alloc1.var_name = Some("".to_string()); alloc1.type_name = Some("()".to_string()); edge_allocations.push(alloc1);
1465
1466 let mut alloc2 = create_test_allocation(0x2000, 1000000); alloc2.borrow_count = 1000; alloc2.var_name = Some("very_long_variable_name_".repeat(10)); alloc2.type_name = Some("VeryComplexType<T, U, V>".to_string());
1471 edge_allocations.push(alloc2);
1472
1473 let mut alloc3 = create_test_allocation(0x3000, 256);
1475 alloc3.var_name = Some("var_with_unicode_🦀_and_symbols".to_string());
1476 alloc3.type_name = Some("Type\"With'Quotes<&str>".to_string());
1477 edge_allocations.push(alloc3);
1478
1479 let memory_stats = MemoryStats {
1480 total_allocations: 3,
1481 total_allocated: 1000256, active_allocations: 3,
1483 active_memory: 1000256,
1484 peak_allocations: 3,
1485 peak_memory: 1000256,
1486 total_deallocations: 0,
1487 total_deallocated: 0,
1488 leaked_allocations: 0,
1489 leaked_memory: 0,
1490 fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
1491 lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
1492 allocations: edge_allocations,
1493 system_library_stats: crate::core::types::SystemLibraryStats::default(),
1494 concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
1495 };
1496
1497 let unsafe_reports = vec![];
1498 let memory_passports = vec![];
1499
1500 let result = exporter.export_enhanced_analysis(
1502 &temp_dir,
1503 &memory_stats,
1504 &unsafe_reports,
1505 &memory_passports,
1506 );
1507
1508 assert!(result.is_ok());
1509
1510 assert!(temp_dir.path().join("memory_analysis.json").exists());
1512
1513 let memory_content = std::fs::read_to_string(temp_dir.path().join("memory_analysis.json"))
1515 .map_err(|e| ExportError(e.to_string()))?;
1516 let memory_json: serde_json::Value =
1517 serde_json::from_str(&memory_content).map_err(|e| SerializationError(e.to_string()))?;
1518
1519 let allocations = memory_json["allocations"].as_array().unwrap();
1520 assert_eq!(allocations.len(), 3);
1521
1522 let zero_alloc = allocations
1524 .iter()
1525 .find(|a| a["size"].as_u64().unwrap() == 0);
1526 assert!(zero_alloc.is_some());
1527
1528 let unicode_alloc = allocations
1530 .iter()
1531 .find(|a| a["var_name"].as_str().unwrap_or("").contains("🦀"));
1532 assert!(unicode_alloc.is_some());
1533
1534 Ok(())
1535 }
1536}