1use super::ownership_history::{BorrowInfo, CloneInfo, OwnershipHistoryRecorder, OwnershipSummary};
7use crate::capture::types::AllocationInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11pub struct LifecycleSummaryGenerator {
13 config: SummaryConfig,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SummaryConfig {
20 pub include_borrow_details: bool,
22 pub include_clone_details: bool,
24 pub min_lifetime_threshold_ms: u64,
26 pub max_events_per_allocation: usize,
28}
29
30impl Default for SummaryConfig {
31 fn default() -> Self {
32 Self {
33 include_borrow_details: true,
34 include_clone_details: true,
35 min_lifetime_threshold_ms: 0,
36 max_events_per_allocation: 50,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LifecycleExportData {
44 pub lifecycle_events: Vec<LifecycleEventSummary>,
46 pub variable_groups: Vec<VariableGroup>,
48 pub user_variables_count: usize,
50 pub visualization_ready: bool,
52 pub metadata: ExportMetadata,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LifecycleEventSummary {
59 pub allocation_ptr: usize,
61 pub var_name: Option<String>,
63 pub type_name: Option<String>,
65 pub size: usize,
67 pub lifetime_ms: Option<u64>,
69 pub events: Vec<LifecycleEvent>,
71 pub summary: AllocationLifecycleSummary,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct LifecycleEvent {
78 pub id: u64,
80 pub event_type: String,
82 pub timestamp: u64,
84 pub size: Option<usize>,
86 pub details: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct AllocationLifecycleSummary {
93 pub lifetime_ms: Option<u64>,
95 pub borrow_info: BorrowInfo,
97 pub clone_info: CloneInfo,
99 pub ownership_history_available: bool,
101 pub lifecycle_pattern: LifecyclePattern,
103 pub efficiency_score: f64,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum LifecyclePattern {
110 Ephemeral,
112 ShortTerm,
114 MediumTerm,
116 LongTerm,
118 Leaked,
120 Unknown,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct VariableGroup {
127 pub name: String,
129 pub variables: Vec<String>,
131 pub total_memory: usize,
133 pub average_lifetime_ms: f64,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ExportMetadata {
140 pub export_timestamp: u64,
142 pub total_allocations: usize,
144 pub total_events: usize,
146 pub analysis_duration_ms: u64,
148}
149
150impl LifecycleSummaryGenerator {
151 pub fn new() -> Self {
153 Self::with_config(SummaryConfig::default())
154 }
155
156 pub fn with_config(config: SummaryConfig) -> Self {
158 Self { config }
159 }
160
161 pub fn generate_lifecycle_export(
163 &self,
164 ownership_history: &OwnershipHistoryRecorder,
165 allocations: &[AllocationInfo],
166 ) -> LifecycleExportData {
167 let start_time = std::time::Instant::now();
168
169 let lifecycle_events = self.generate_lifecycle_events(ownership_history, allocations);
171
172 let variable_groups = self.generate_variable_groups(&lifecycle_events);
174
175 let user_variables_count = lifecycle_events
177 .iter()
178 .filter(|event| {
179 event
180 .var_name
181 .as_ref()
182 .map(|name| self.is_user_variable(name))
183 .unwrap_or(false)
184 })
185 .count();
186
187 let analysis_duration = start_time.elapsed().as_millis() as u64;
188
189 LifecycleExportData {
190 lifecycle_events,
191 variable_groups,
192 user_variables_count,
193 visualization_ready: true,
194 metadata: ExportMetadata {
195 export_timestamp: self.get_current_timestamp(),
196 total_allocations: allocations.len(),
197 total_events: ownership_history.get_statistics().total_events,
198 analysis_duration_ms: analysis_duration,
199 },
200 }
201 }
202
203 fn generate_lifecycle_events(
205 &self,
206 ownership_history: &OwnershipHistoryRecorder,
207 allocations: &[AllocationInfo],
208 ) -> Vec<LifecycleEventSummary> {
209 let mut summaries = Vec::new();
210
211 for allocation in allocations {
212 if let Some(lifetime_ms) = allocation.lifetime_ms {
214 if lifetime_ms < self.config.min_lifetime_threshold_ms {
215 continue;
216 }
217 }
218
219 let summary = self.generate_single_lifecycle_summary(ownership_history, allocation);
220 summaries.push(summary);
221 }
222
223 summaries
224 }
225
226 fn generate_single_lifecycle_summary(
228 &self,
229 ownership_history: &OwnershipHistoryRecorder,
230 allocation: &AllocationInfo,
231 ) -> LifecycleEventSummary {
232 let ptr = allocation.ptr;
233
234 let ownership_summary = ownership_history.get_summary(ptr);
236
237 let events = if let Some(ownership_events) = ownership_history.get_events(ptr) {
239 ownership_events
240 .iter()
241 .take(self.config.max_events_per_allocation)
242 .map(|event| LifecycleEvent {
243 id: event.event_id,
244 event_type: self.format_event_type(&event.event_type),
245 timestamp: event.timestamp,
246 size: Some(allocation.size),
247 details: event.details.context.clone(),
248 })
249 .collect()
250 } else {
251 let mut basic_events = vec![LifecycleEvent {
253 id: 1,
254 event_type: "Allocation".to_string(),
255 timestamp: allocation.timestamp_alloc,
256 size: Some(allocation.size),
257 details: Some("Memory allocated".to_string()),
258 }];
259
260 if let Some(dealloc_time) = allocation.timestamp_dealloc {
261 basic_events.push(LifecycleEvent {
262 id: 2,
263 event_type: "Deallocation".to_string(),
264 timestamp: dealloc_time,
265 size: Some(allocation.size),
266 details: Some("Memory deallocated".to_string()),
267 });
268 }
269
270 basic_events
271 };
272
273 let summary = if let Some(ownership_summary) = ownership_summary {
275 AllocationLifecycleSummary {
276 lifetime_ms: allocation.lifetime_ms,
277 borrow_info: ownership_summary.borrow_info.clone(),
278 clone_info: ownership_summary.clone_info.clone(),
279 ownership_history_available: true,
280 lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
281 efficiency_score: self.calculate_efficiency_score(allocation, ownership_summary),
282 }
283 } else {
284 AllocationLifecycleSummary {
285 lifetime_ms: allocation.lifetime_ms,
286 borrow_info: BorrowInfo {
287 immutable_borrows: 0,
288 mutable_borrows: 0,
289 max_concurrent_borrows: 0,
290 last_borrow_timestamp: None,
291 active_borrows: Vec::new(),
292 },
293 clone_info: CloneInfo {
294 clone_count: 0,
295 is_clone: false,
296 original_ptr: None,
297 cloned_ptrs: Vec::new(),
298 },
299 ownership_history_available: false,
300 lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
301 efficiency_score: 0.5, }
303 };
304
305 LifecycleEventSummary {
306 allocation_ptr: ptr,
307 var_name: allocation.var_name.clone(),
308 type_name: allocation.type_name.clone(),
309 size: allocation.size,
310 lifetime_ms: allocation.lifetime_ms,
311 events,
312 summary,
313 }
314 }
315
316 fn format_event_type(
318 &self,
319 event_type: &super::ownership_history::OwnershipEventType,
320 ) -> String {
321 match event_type {
322 super::ownership_history::OwnershipEventType::Allocated => "Allocation".to_string(),
323 super::ownership_history::OwnershipEventType::Cloned { .. } => "Clone".to_string(),
324 super::ownership_history::OwnershipEventType::Dropped => "Deallocation".to_string(),
325 super::ownership_history::OwnershipEventType::OwnershipTransferred { .. } => {
326 "OwnershipTransfer".to_string()
327 }
328 super::ownership_history::OwnershipEventType::Borrowed { .. } => "Borrow".to_string(),
329 super::ownership_history::OwnershipEventType::MutablyBorrowed { .. } => {
330 "MutableBorrow".to_string()
331 }
332 super::ownership_history::OwnershipEventType::BorrowReleased { .. } => {
333 "BorrowRelease".to_string()
334 }
335 super::ownership_history::OwnershipEventType::RefCountChanged { .. } => {
336 "RefCountChange".to_string()
337 }
338 }
339 }
340
341 fn classify_lifecycle_pattern(&self, lifetime_ms: Option<u64>) -> LifecyclePattern {
343 match lifetime_ms {
344 None => LifecyclePattern::Leaked,
345 Some(0) => LifecyclePattern::Ephemeral,
346 Some(ms) if ms < 1 => LifecyclePattern::Ephemeral,
347 Some(ms) if ms < 100 => LifecyclePattern::ShortTerm,
348 Some(ms) if ms < 10_000 => LifecyclePattern::MediumTerm,
349 Some(_) => LifecyclePattern::LongTerm,
350 }
351 }
352
353 fn calculate_efficiency_score(
355 &self,
356 allocation: &AllocationInfo,
357 ownership_summary: &OwnershipSummary,
358 ) -> f64 {
359 let mut score: f64 = 0.5; if allocation
363 .var_name
364 .as_ref()
365 .map(|name| self.is_user_variable(name))
366 .unwrap_or(false)
367 {
368 score += 0.1;
369 }
370
371 match self.classify_lifecycle_pattern(allocation.lifetime_ms) {
373 LifecyclePattern::ShortTerm | LifecyclePattern::MediumTerm => score += 0.2,
374 LifecyclePattern::Ephemeral => score -= 0.1,
375 LifecyclePattern::Leaked => score -= 0.3,
376 _ => {}
377 }
378
379 if ownership_summary.borrow_info.max_concurrent_borrows > 5 {
381 score -= 0.1;
382 }
383
384 if ownership_summary.clone_info.clone_count > 0 || ownership_summary.clone_info.is_clone {
386 score += 0.1;
387 }
388
389 score.clamp(0.0, 1.0)
391 }
392
393 fn is_user_variable(&self, name: &str) -> bool {
395 !name.starts_with("primitive_")
397 && !name.starts_with("struct_")
398 && !name.starts_with("collection_")
399 && !name.starts_with("buffer_")
400 && !name.starts_with("system_")
401 && !name.starts_with("fast_tracked")
402 && name != "unknown"
403 }
404
405 fn generate_variable_groups(
407 &self,
408 lifecycle_events: &[LifecycleEventSummary],
409 ) -> Vec<VariableGroup> {
410 let mut groups: HashMap<String, Vec<&LifecycleEventSummary>> = HashMap::new();
411
412 for event in lifecycle_events {
414 if let Some(ref type_name) = event.type_name {
415 let group_name = self.extract_base_type_name(type_name);
416 groups.entry(group_name).or_default().push(event);
417 }
418 }
419
420 groups
422 .into_iter()
423 .map(|(name, events)| {
424 let variables: Vec<String> =
425 events.iter().filter_map(|e| e.var_name.clone()).collect();
426
427 let total_memory: usize = events.iter().map(|e| e.size).sum();
428
429 let average_lifetime_ms = if !events.is_empty() {
430 let total_lifetime: u64 = events.iter().filter_map(|e| e.lifetime_ms).sum();
431 let count = events.iter().filter(|e| e.lifetime_ms.is_some()).count();
432 if count > 0 {
433 total_lifetime as f64 / count as f64
434 } else {
435 0.0
436 }
437 } else {
438 0.0
439 };
440
441 VariableGroup {
442 name,
443 variables,
444 total_memory,
445 average_lifetime_ms,
446 }
447 })
448 .collect()
449 }
450
451 fn extract_base_type_name(&self, type_name: &str) -> String {
453 if let Some(pos) = type_name.find('<') {
455 type_name[..pos].to_string()
456 } else if let Some(pos) = type_name.rfind("::") {
457 type_name[pos + 2..].to_string()
458 } else {
459 type_name.to_string()
460 }
461 }
462
463 fn get_current_timestamp(&self) -> u64 {
465 std::time::SystemTime::now()
466 .duration_since(std::time::UNIX_EPOCH)
467 .unwrap_or_default()
468 .as_nanos() as u64
469 }
470
471 pub fn export_to_json(&self, export_data: &LifecycleExportData) -> serde_json::Result<String> {
473 serde_json::to_string_pretty(export_data)
474 }
475}
476
477impl Default for LifecycleSummaryGenerator {
478 fn default() -> Self {
479 Self::new()
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
490 fn test_lifecycle_summary_generator_creation() {
491 let generator = LifecycleSummaryGenerator::new();
492 assert!(
493 generator.config.include_borrow_details,
494 "Default include_borrow_details should be true"
495 );
496 assert!(
497 generator.config.include_clone_details,
498 "Default include_clone_details should be true"
499 );
500 }
501
502 #[test]
505 fn test_lifecycle_summary_generator_default() {
506 let generator = LifecycleSummaryGenerator::default();
507 assert!(
508 generator.config.include_borrow_details,
509 "Default should have include_borrow_details=true"
510 );
511 }
512
513 #[test]
516 fn test_lifecycle_summary_generator_with_config() {
517 let config = SummaryConfig {
518 include_borrow_details: false,
519 include_clone_details: false,
520 min_lifetime_threshold_ms: 100,
521 max_events_per_allocation: 10,
522 };
523
524 let generator = LifecycleSummaryGenerator::with_config(config);
525
526 assert!(
527 !generator.config.include_borrow_details,
528 "Custom include_borrow_details should be false"
529 );
530 assert!(
531 !generator.config.include_clone_details,
532 "Custom include_clone_details should be false"
533 );
534 assert_eq!(
535 generator.config.min_lifetime_threshold_ms, 100,
536 "Custom min_lifetime_threshold_ms should be 100"
537 );
538 assert_eq!(
539 generator.config.max_events_per_allocation, 10,
540 "Custom max_events_per_allocation should be 10"
541 );
542 }
543
544 #[test]
547 fn test_lifecycle_pattern_classification() {
548 let generator = LifecycleSummaryGenerator::default();
549
550 assert!(
551 matches!(
552 generator.classify_lifecycle_pattern(None),
553 LifecyclePattern::Leaked
554 ),
555 "None should be Leaked"
556 );
557 assert!(
558 matches!(
559 generator.classify_lifecycle_pattern(Some(0)),
560 LifecyclePattern::Ephemeral
561 ),
562 "0ms should be Ephemeral"
563 );
564 assert!(
565 matches!(
566 generator.classify_lifecycle_pattern(Some(50)),
567 LifecyclePattern::ShortTerm
568 ),
569 "50ms should be ShortTerm"
570 );
571 assert!(
572 matches!(
573 generator.classify_lifecycle_pattern(Some(5000)),
574 LifecyclePattern::MediumTerm
575 ),
576 "5000ms should be MediumTerm"
577 );
578 assert!(
579 matches!(
580 generator.classify_lifecycle_pattern(Some(15000)),
581 LifecyclePattern::LongTerm
582 ),
583 "15000ms should be LongTerm"
584 );
585 }
586
587 #[test]
590 fn test_lifecycle_pattern_classification_boundaries() {
591 let generator = LifecycleSummaryGenerator::default();
592
593 assert!(
594 matches!(
595 generator.classify_lifecycle_pattern(Some(1)),
596 LifecyclePattern::ShortTerm
597 ),
598 "1ms should be ShortTerm"
599 );
600 assert!(
601 matches!(
602 generator.classify_lifecycle_pattern(Some(99)),
603 LifecyclePattern::ShortTerm
604 ),
605 "99ms should be ShortTerm"
606 );
607 assert!(
608 matches!(
609 generator.classify_lifecycle_pattern(Some(100)),
610 LifecyclePattern::MediumTerm
611 ),
612 "100ms should be MediumTerm"
613 );
614 assert!(
615 matches!(
616 generator.classify_lifecycle_pattern(Some(9999)),
617 LifecyclePattern::MediumTerm
618 ),
619 "9999ms should be MediumTerm"
620 );
621 assert!(
622 matches!(
623 generator.classify_lifecycle_pattern(Some(10000)),
624 LifecyclePattern::LongTerm
625 ),
626 "10000ms should be LongTerm"
627 );
628 }
629
630 #[test]
633 fn test_is_user_variable_valid() {
634 let generator = LifecycleSummaryGenerator::default();
635
636 assert!(
637 generator.is_user_variable("my_variable"),
638 "my_variable should be user variable"
639 );
640 assert!(
641 generator.is_user_variable("data"),
642 "data should be user variable"
643 );
644 assert!(
645 generator.is_user_variable("result"),
646 "result should be user variable"
647 );
648 }
649
650 #[test]
653 fn test_is_user_variable_system() {
654 let generator = LifecycleSummaryGenerator::default();
655
656 assert!(
657 !generator.is_user_variable("primitive_int"),
658 "primitive_int should not be user variable"
659 );
660 assert!(
661 !generator.is_user_variable("struct_Data"),
662 "struct_Data should not be user variable"
663 );
664 assert!(
665 !generator.is_user_variable("collection_vec"),
666 "collection_vec should not be user variable"
667 );
668 assert!(
669 !generator.is_user_variable("buffer_data"),
670 "buffer_data should not be user variable"
671 );
672 assert!(
673 !generator.is_user_variable("system_temp"),
674 "system_temp should not be user variable"
675 );
676 assert!(
677 !generator.is_user_variable("fast_tracked_123"),
678 "fast_tracked should not be user variable"
679 );
680 assert!(
681 !generator.is_user_variable("unknown"),
682 "unknown should not be user variable"
683 );
684 }
685
686 #[test]
689 fn test_extract_base_type_name_generic() {
690 let generator = LifecycleSummaryGenerator::default();
691
692 assert_eq!(
693 generator.extract_base_type_name("Vec<i32>"),
694 "Vec",
695 "Should extract Vec from Vec<i32>"
696 );
697 assert_eq!(
698 generator.extract_base_type_name("HashMap<String, i32>"),
699 "HashMap",
700 "Should extract HashMap from HashMap<String, i32>"
701 );
702 assert_eq!(
703 generator.extract_base_type_name("Option<String>"),
704 "Option",
705 "Should extract Option from Option<String>"
706 );
707 }
708
709 #[test]
712 fn test_extract_base_type_name_module_path() {
713 let generator = LifecycleSummaryGenerator::default();
714
715 assert_eq!(
716 generator.extract_base_type_name("std::collections::HashMap"),
717 "HashMap",
718 "Should extract HashMap from module path"
719 );
720 assert_eq!(
721 generator.extract_base_type_name("my_module::MyType"),
722 "MyType",
723 "Should extract MyType from module path"
724 );
725 }
726
727 #[test]
730 fn test_extract_base_type_name_simple() {
731 let generator = LifecycleSummaryGenerator::default();
732
733 assert_eq!(
734 generator.extract_base_type_name("String"),
735 "String",
736 "Should return String for String"
737 );
738 assert_eq!(
739 generator.extract_base_type_name("i32"),
740 "i32",
741 "Should return i32 for i32"
742 );
743 }
744
745 #[test]
748 fn test_json_export_basic() {
749 let generator = LifecycleSummaryGenerator::default();
750 let export_data = LifecycleExportData {
751 lifecycle_events: vec![],
752 variable_groups: vec![],
753 user_variables_count: 0,
754 visualization_ready: true,
755 metadata: ExportMetadata {
756 export_timestamp: 0,
757 total_allocations: 0,
758 total_events: 0,
759 analysis_duration_ms: 0,
760 },
761 };
762
763 let json = generator.export_to_json(&export_data).unwrap();
764 assert!(
765 json.contains("lifecycle_events"),
766 "JSON should contain lifecycle_events"
767 );
768 assert!(
769 json.contains("variable_groups"),
770 "JSON should contain variable_groups"
771 );
772 assert!(json.contains("metadata"), "JSON should contain metadata");
773 }
774
775 #[test]
778 fn test_json_export_with_events() {
779 let generator = LifecycleSummaryGenerator::default();
780
781 let event_summary = LifecycleEventSummary {
782 allocation_ptr: 0x1000,
783 var_name: Some("test_var".to_string()),
784 type_name: Some("String".to_string()),
785 size: 1024,
786 lifetime_ms: Some(1000),
787 events: vec![],
788 summary: AllocationLifecycleSummary {
789 lifetime_ms: Some(1000),
790 borrow_info: BorrowInfo {
791 immutable_borrows: 0,
792 mutable_borrows: 0,
793 max_concurrent_borrows: 0,
794 last_borrow_timestamp: None,
795 active_borrows: vec![],
796 },
797 clone_info: CloneInfo {
798 clone_count: 0,
799 is_clone: false,
800 original_ptr: None,
801 cloned_ptrs: vec![],
802 },
803 ownership_history_available: false,
804 lifecycle_pattern: LifecyclePattern::ShortTerm,
805 efficiency_score: 0.8,
806 },
807 };
808
809 let export_data = LifecycleExportData {
810 lifecycle_events: vec![event_summary],
811 variable_groups: vec![],
812 user_variables_count: 1,
813 visualization_ready: true,
814 metadata: ExportMetadata {
815 export_timestamp: 1000,
816 total_allocations: 1,
817 total_events: 1,
818 analysis_duration_ms: 100,
819 },
820 };
821
822 let json = generator.export_to_json(&export_data).unwrap();
823 assert!(
824 json.contains("test_var"),
825 "JSON should contain variable name"
826 );
827 assert!(json.contains("String"), "JSON should contain type name");
828 assert!(
829 json.contains("ShortTerm"),
830 "JSON should contain lifecycle pattern"
831 );
832 }
833
834 #[test]
837 fn test_summary_config_default() {
838 let config = SummaryConfig::default();
839 assert!(
840 config.include_borrow_details,
841 "Default include_borrow_details should be true"
842 );
843 assert!(
844 config.include_clone_details,
845 "Default include_clone_details should be true"
846 );
847 assert_eq!(
848 config.min_lifetime_threshold_ms, 0,
849 "Default min_lifetime_threshold_ms should be 0"
850 );
851 assert_eq!(
852 config.max_events_per_allocation, 50,
853 "Default max_events_per_allocation should be 50"
854 );
855 }
856
857 #[test]
860 fn test_lifecycle_export_metadata() {
861 let metadata = ExportMetadata {
862 export_timestamp: 1234567890,
863 total_allocations: 100,
864 total_events: 500,
865 analysis_duration_ms: 1000,
866 };
867
868 assert_eq!(
869 metadata.export_timestamp, 1234567890,
870 "export_timestamp should match"
871 );
872 assert_eq!(
873 metadata.total_allocations, 100,
874 "total_allocations should match"
875 );
876 assert_eq!(metadata.total_events, 500, "total_events should match");
877 assert_eq!(
878 metadata.analysis_duration_ms, 1000,
879 "analysis_duration_ms should match"
880 );
881 }
882
883 #[test]
886 fn test_lifecycle_event_serialization() {
887 let event = LifecycleEvent {
888 id: 1,
889 event_type: "Allocation".to_string(),
890 timestamp: 1000,
891 size: Some(1024),
892 details: Some("Memory allocated".to_string()),
893 };
894
895 let json = serde_json::to_string(&event).unwrap();
896 assert!(
897 json.contains("Allocation"),
898 "JSON should contain event_type"
899 );
900 assert!(json.contains("1024"), "JSON should contain size");
901 }
902
903 #[test]
906 fn test_lifecycle_event_with_none_fields() {
907 let event = LifecycleEvent {
908 id: 2,
909 event_type: "Deallocation".to_string(),
910 timestamp: 2000,
911 size: None,
912 details: None,
913 };
914
915 let json = serde_json::to_string(&event).unwrap();
916 assert!(
917 json.contains("Deallocation"),
918 "JSON should contain event_type"
919 );
920 assert!(
921 json.contains("null"),
922 "JSON should contain null for None fields"
923 );
924 }
925
926 #[test]
929 fn test_lifecycle_pattern_serialization() {
930 let patterns = vec![
931 LifecyclePattern::Ephemeral,
932 LifecyclePattern::ShortTerm,
933 LifecyclePattern::MediumTerm,
934 LifecyclePattern::LongTerm,
935 LifecyclePattern::Leaked,
936 LifecyclePattern::Unknown,
937 ];
938
939 for pattern in patterns {
940 let json = serde_json::to_string(&pattern).unwrap();
941 assert!(
942 !json.is_empty(),
943 "Pattern should serialize to non-empty JSON"
944 );
945 let deserialized: LifecyclePattern = serde_json::from_str(&json).unwrap();
946 assert_eq!(
947 format!("{:?}", pattern),
948 format!("{:?}", deserialized),
949 "Pattern should deserialize correctly"
950 );
951 }
952 }
953
954 #[test]
957 fn test_variable_group() {
958 let group = VariableGroup {
959 name: "String".to_string(),
960 variables: vec!["var1".to_string(), "var2".to_string()],
961 total_memory: 2048,
962 average_lifetime_ms: 500.0,
963 };
964
965 assert_eq!(group.name, "String", "name should match");
966 assert_eq!(group.variables.len(), 2, "variables count should match");
967 assert_eq!(group.total_memory, 2048, "total_memory should match");
968 assert_eq!(
969 group.average_lifetime_ms, 500.0,
970 "average_lifetime_ms should match"
971 );
972 }
973
974 #[test]
977 fn test_variable_group_serialization() {
978 let group = VariableGroup {
979 name: "Vec".to_string(),
980 variables: vec!["data".to_string()],
981 total_memory: 1024,
982 average_lifetime_ms: 100.0,
983 };
984
985 let json = serde_json::to_string(&group).unwrap();
986 assert!(json.contains("Vec"), "JSON should contain group name");
987 assert!(json.contains("data"), "JSON should contain variable name");
988 }
989
990 #[test]
993 fn test_allocation_lifecycle_summary() {
994 let summary = AllocationLifecycleSummary {
995 lifetime_ms: Some(1000),
996 borrow_info: BorrowInfo {
997 immutable_borrows: 5,
998 mutable_borrows: 2,
999 max_concurrent_borrows: 3,
1000 last_borrow_timestamp: Some(500),
1001 active_borrows: vec![],
1002 },
1003 clone_info: CloneInfo {
1004 clone_count: 1,
1005 is_clone: false,
1006 original_ptr: None,
1007 cloned_ptrs: vec![0x2000],
1008 },
1009 ownership_history_available: true,
1010 lifecycle_pattern: LifecyclePattern::MediumTerm,
1011 efficiency_score: 0.75,
1012 };
1013
1014 assert_eq!(summary.lifetime_ms, Some(1000), "lifetime_ms should match");
1015 assert_eq!(
1016 summary.borrow_info.immutable_borrows, 5,
1017 "immutable_borrows should match"
1018 );
1019 assert_eq!(
1020 summary.clone_info.clone_count, 1,
1021 "clone_count should match"
1022 );
1023 assert!(
1024 summary.ownership_history_available,
1025 "ownership_history_available should be true"
1026 );
1027 assert_eq!(
1028 summary.efficiency_score, 0.75,
1029 "efficiency_score should match"
1030 );
1031 }
1032
1033 #[test]
1036 fn test_get_current_timestamp() {
1037 let generator = LifecycleSummaryGenerator::default();
1038 let ts = generator.get_current_timestamp();
1039
1040 assert!(ts > 0, "Timestamp should be positive");
1041 }
1042
1043 #[test]
1046 fn test_lifecycle_event_summary() {
1047 let summary = LifecycleEventSummary {
1048 allocation_ptr: 0x1000,
1049 var_name: Some("test".to_string()),
1050 type_name: Some("i32".to_string()),
1051 size: 4,
1052 lifetime_ms: Some(100),
1053 events: vec![],
1054 summary: AllocationLifecycleSummary {
1055 lifetime_ms: Some(100),
1056 borrow_info: BorrowInfo {
1057 immutable_borrows: 0,
1058 mutable_borrows: 0,
1059 max_concurrent_borrows: 0,
1060 last_borrow_timestamp: None,
1061 active_borrows: vec![],
1062 },
1063 clone_info: CloneInfo {
1064 clone_count: 0,
1065 is_clone: false,
1066 original_ptr: None,
1067 cloned_ptrs: vec![],
1068 },
1069 ownership_history_available: false,
1070 lifecycle_pattern: LifecyclePattern::ShortTerm,
1071 efficiency_score: 0.5,
1072 },
1073 };
1074
1075 assert_eq!(
1076 summary.allocation_ptr, 0x1000,
1077 "allocation_ptr should match"
1078 );
1079 assert_eq!(
1080 summary.var_name,
1081 Some("test".to_string()),
1082 "var_name should match"
1083 );
1084 assert_eq!(summary.size, 4, "size should match");
1085 }
1086
1087 #[test]
1090 fn test_summary_config_clone() {
1091 let original = SummaryConfig {
1092 include_borrow_details: false,
1093 include_clone_details: true,
1094 min_lifetime_threshold_ms: 50,
1095 max_events_per_allocation: 25,
1096 };
1097
1098 let cloned = original.clone();
1099
1100 assert_eq!(
1101 original.include_borrow_details, cloned.include_borrow_details,
1102 "Cloned include_borrow_details should match"
1103 );
1104 assert_eq!(
1105 original.min_lifetime_threshold_ms, cloned.min_lifetime_threshold_ms,
1106 "Cloned min_lifetime_threshold_ms should match"
1107 );
1108 }
1109
1110 #[test]
1113 fn test_lifecycle_export_data_clone() {
1114 let original = LifecycleExportData {
1115 lifecycle_events: vec![],
1116 variable_groups: vec![],
1117 user_variables_count: 5,
1118 visualization_ready: true,
1119 metadata: ExportMetadata {
1120 export_timestamp: 1000,
1121 total_allocations: 10,
1122 total_events: 20,
1123 analysis_duration_ms: 50,
1124 },
1125 };
1126
1127 let cloned = original.clone();
1128
1129 assert_eq!(
1130 original.user_variables_count, cloned.user_variables_count,
1131 "Cloned user_variables_count should match"
1132 );
1133 assert_eq!(
1134 original.visualization_ready, cloned.visualization_ready,
1135 "Cloned visualization_ready should match"
1136 );
1137 }
1138
1139 #[test]
1142 fn test_generate_lifecycle_export_empty() {
1143 let generator = LifecycleSummaryGenerator::default();
1144 let ownership_history = OwnershipHistoryRecorder::new();
1145 let allocations: Vec<AllocationInfo> = vec![];
1146
1147 let export_data = generator.generate_lifecycle_export(&ownership_history, &allocations);
1148
1149 assert_eq!(
1150 export_data.lifecycle_events.len(),
1151 0,
1152 "Should have no lifecycle events"
1153 );
1154 assert_eq!(
1155 export_data.variable_groups.len(),
1156 0,
1157 "Should have no variable groups"
1158 );
1159 assert_eq!(
1160 export_data.user_variables_count, 0,
1161 "Should have 0 user variables"
1162 );
1163 assert_eq!(
1164 export_data.metadata.total_allocations, 0,
1165 "Should have 0 total allocations"
1166 );
1167 }
1168}