Skip to main content

memscope_rs/analysis/lifecycle/
lifecycle_summary.rs

1//! Lifecycle summary generation for enhanced memory analysis
2//!
3//! This module provides functionality to generate high-level lifecycle summaries
4//! from detailed ownership history, creating the data needed for lifetime.json export.
5
6use super::ownership_history::{BorrowInfo, CloneInfo, OwnershipHistoryRecorder, OwnershipSummary};
7use crate::capture::types::AllocationInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Generator for lifecycle summaries and lifetime.json export
12pub struct LifecycleSummaryGenerator {
13    /// Configuration for summary generation
14    config: SummaryConfig,
15}
16
17/// Configuration for lifecycle summary generation
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SummaryConfig {
20    /// Include detailed borrow information
21    pub include_borrow_details: bool,
22    /// Include clone relationship information
23    pub include_clone_details: bool,
24    /// Minimum lifetime threshold for inclusion (in milliseconds)
25    pub min_lifetime_threshold_ms: u64,
26    /// Maximum number of lifecycle events to include per allocation
27    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/// Complete lifecycle data for export to lifetime.json
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LifecycleExportData {
44    /// Lifecycle events for each allocation
45    pub lifecycle_events: Vec<LifecycleEventSummary>,
46    /// Variable groups for organization
47    pub variable_groups: Vec<VariableGroup>,
48    /// Count of user variables (those with meaningful names)
49    pub user_variables_count: usize,
50    /// Whether visualization data is ready
51    pub visualization_ready: bool,
52    /// Export metadata
53    pub metadata: ExportMetadata,
54}
55
56/// Summary of lifecycle events for a single allocation
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LifecycleEventSummary {
59    /// Allocation pointer
60    pub allocation_ptr: usize,
61    /// Variable name (if available)
62    pub var_name: Option<String>,
63    /// Type name
64    pub type_name: Option<String>,
65    /// Size of the allocation
66    pub size: usize,
67    /// Total lifetime in milliseconds
68    pub lifetime_ms: Option<u64>,
69    /// Lifecycle events
70    pub events: Vec<LifecycleEvent>,
71    /// High-level summary
72    pub summary: AllocationLifecycleSummary,
73}
74
75/// Individual lifecycle event
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct LifecycleEvent {
78    /// Event ID
79    pub id: u64,
80    /// Event type
81    pub event_type: String,
82    /// Timestamp when the event occurred
83    pub timestamp: u64,
84    /// Size involved in the event (if applicable)
85    pub size: Option<usize>,
86    /// Additional event details
87    pub details: Option<String>,
88}
89
90/// High-level summary of an allocation's lifecycle
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct AllocationLifecycleSummary {
93    /// Total lifetime in milliseconds
94    pub lifetime_ms: Option<u64>,
95    /// Borrowing information
96    pub borrow_info: BorrowInfo,
97    /// Cloning information
98    pub clone_info: CloneInfo,
99    /// Whether detailed ownership history is available
100    pub ownership_history_available: bool,
101    /// Lifecycle pattern classification
102    pub lifecycle_pattern: LifecyclePattern,
103    /// Memory efficiency score (0.0 to 1.0)
104    pub efficiency_score: f64,
105}
106
107/// Classification of lifecycle patterns
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum LifecyclePattern {
110    /// Short-lived allocation (< 1ms)
111    Ephemeral,
112    /// Short-term allocation (1ms - 100ms)
113    ShortTerm,
114    /// Medium-term allocation (100ms - 10s)
115    MediumTerm,
116    /// Long-term allocation (> 10s)
117    LongTerm,
118    /// Leaked allocation (never deallocated)
119    Leaked,
120    /// Unknown pattern
121    Unknown,
122}
123
124/// Group of related variables
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct VariableGroup {
127    /// Group name
128    pub name: String,
129    /// Variables in this group
130    pub variables: Vec<String>,
131    /// Total memory used by this group
132    pub total_memory: usize,
133    /// Average lifetime of variables in this group
134    pub average_lifetime_ms: f64,
135}
136
137/// Export metadata
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ExportMetadata {
140    /// Export timestamp
141    pub export_timestamp: u64,
142    /// Total allocations analyzed
143    pub total_allocations: usize,
144    /// Total events processed
145    pub total_events: usize,
146    /// Analysis duration in milliseconds
147    pub analysis_duration_ms: u64,
148}
149
150impl LifecycleSummaryGenerator {
151    /// Create a new lifecycle summary generator
152    pub fn new() -> Self {
153        Self::with_config(SummaryConfig::default())
154    }
155
156    /// Create a new lifecycle summary generator with custom configuration
157    pub fn with_config(config: SummaryConfig) -> Self {
158        Self { config }
159    }
160
161    /// Generate complete lifecycle export data
162    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        // Generate lifecycle event summaries
170        let lifecycle_events = self.generate_lifecycle_events(ownership_history, allocations);
171
172        // Generate variable groups
173        let variable_groups = self.generate_variable_groups(&lifecycle_events);
174
175        // Count user variables (those with meaningful names)
176        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    /// Generate lifecycle event summaries for all allocations
204    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            // Skip allocations below the minimum lifetime threshold
213            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    /// Generate lifecycle summary for a single allocation
227    fn generate_single_lifecycle_summary(
228        &self,
229        ownership_history: &OwnershipHistoryRecorder,
230        allocation: &AllocationInfo,
231    ) -> LifecycleEventSummary {
232        let ptr = allocation.ptr;
233
234        // Get ownership summary if available
235        let ownership_summary = ownership_history.get_summary(ptr);
236
237        // Generate lifecycle events
238        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            // Create basic events from allocation info
252            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        // Create allocation lifecycle summary
274        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, // Default score
302            }
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    /// Format ownership event type for display
317    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    /// Classify the lifecycle pattern based on lifetime
342    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    /// Calculate efficiency score for an allocation
354    fn calculate_efficiency_score(
355        &self,
356        allocation: &AllocationInfo,
357        ownership_summary: &OwnershipSummary,
358    ) -> f64 {
359        let mut score: f64 = 0.5; // Base score
360
361        // Bonus for having a meaningful variable name
362        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        // Bonus for appropriate lifetime (not too short, not leaked)
372        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        // Penalty for excessive borrowing
380        if ownership_summary.borrow_info.max_concurrent_borrows > 5 {
381            score -= 0.1;
382        }
383
384        // Bonus for being part of a clone chain (indicates reuse)
385        if ownership_summary.clone_info.clone_count > 0 || ownership_summary.clone_info.is_clone {
386            score += 0.1;
387        }
388
389        // Clamp score between 0.0 and 1.0
390        score.clamp(0.0, 1.0)
391    }
392
393    /// Check if a variable name indicates a user-defined variable
394    fn is_user_variable(&self, name: &str) -> bool {
395        // Filter out system-generated names
396        !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    /// Generate variable groups for organization
406    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        // Group by type name
413        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        // Convert to VariableGroup structs
421        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    /// Extract base type name for grouping
452    fn extract_base_type_name(&self, type_name: &str) -> String {
453        // Extract the base type from complex type names
454        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    /// Get current timestamp in nanoseconds
464    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    /// Export lifecycle data to JSON string
472    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    /// Objective: Verify LifecycleSummaryGenerator creation with default config
488    /// Invariants: Default config should have include_borrow_details=true, include_clone_details=true
489    #[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    /// Objective: Verify Default trait implementation
503    /// Invariants: Default should create same as new()
504    #[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    /// Objective: Verify with_config functionality
514    /// Invariants: Custom config should be applied
515    #[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    /// Objective: Verify classify_lifecycle_pattern for all patterns
545    /// Invariants: Should correctly classify all lifecycle patterns
546    #[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    /// Objective: Verify classify_lifecycle_pattern boundary values
588    /// Invariants: Should correctly handle boundary values
589    #[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    /// Objective: Verify is_user_variable with valid names
631    /// Invariants: Should return true for user-defined variable names
632    #[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    /// Objective: Verify is_user_variable with system names
651    /// Invariants: Should return false for system-generated names
652    #[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    /// Objective: Verify extract_base_type_name with generic types
687    /// Invariants: Should extract base type from generic types
688    #[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    /// Objective: Verify extract_base_type_name with module paths
710    /// Invariants: Should extract type name from module path
711    #[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    /// Objective: Verify extract_base_type_name with simple types
728    /// Invariants: Should return same type for simple names
729    #[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    /// Objective: Verify export_to_json with empty data
746    /// Invariants: Should produce valid JSON with all fields
747    #[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    /// Objective: Verify export_to_json with event data
776    /// Invariants: Should include all event details in JSON
777    #[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    /// Objective: Verify SummaryConfig default values
835    /// Invariants: Default should have sensible values
836    #[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    /// Objective: Verify ExportMetadata creation
858    /// Invariants: All fields should be accessible
859    #[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    /// Objective: Verify LifecycleEvent serialization
884    /// Invariants: Should serialize to valid JSON
885    #[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    /// Objective: Verify LifecycleEvent with None fields
904    /// Invariants: Should handle None fields correctly
905    #[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    /// Objective: Verify LifecyclePattern serialization
927    /// Invariants: All patterns should serialize correctly
928    #[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    /// Objective: Verify VariableGroup creation
955    /// Invariants: All fields should be accessible
956    #[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    /// Objective: Verify VariableGroup serialization
975    /// Invariants: Should serialize to valid JSON
976    #[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    /// Objective: Verify AllocationLifecycleSummary creation
991    /// Invariants: All fields should be accessible
992    #[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    /// Objective: Verify get_current_timestamp returns valid value
1034    /// Invariants: Timestamp should be positive
1035    #[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    /// Objective: Verify LifecycleEventSummary creation
1044    /// Invariants: All fields should be accessible
1045    #[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    /// Objective: Verify SummaryConfig clone functionality
1088    /// Invariants: Cloned config should have same values
1089    #[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    /// Objective: Verify LifecycleExportData clone functionality
1111    /// Invariants: Cloned data should have same values
1112    #[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    /// Objective: Verify generate_lifecycle_export with empty allocations
1140    /// Invariants: Should return valid export with empty events
1141    #[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}