Skip to main content

memscope_rs/analysis/closure/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ClosureInfo {
6    pub ptr: usize,
7    pub captures: Vec<CaptureInfo>,
8    pub creation_timestamp: u64,
9    pub thread_id: String,
10    pub call_site: String,
11    pub memory_footprint: ClosureFootprint,
12    pub optimization_potential: OptimizationPotential,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct CaptureInfo {
17    pub var_name: String,
18    pub var_ptr: usize,
19    pub mode: CaptureMode,
20    pub var_type: String,
21    pub size: usize,
22    pub lifetime_bound: Option<String>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum CaptureMode {
27    ByValue,
28    ByReference,
29    ByMutableReference,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ClosureFootprint {
34    pub total_size: usize,
35    pub capture_count: usize,
36    pub by_value_count: usize,
37    pub by_ref_count: usize,
38    pub by_mut_ref_count: usize,
39    pub estimated_heap_usage: usize,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct OptimizationPotential {
44    pub level: OptimizationLevel,
45    pub potential_savings: usize,
46    pub suggestions: Vec<String>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub enum OptimizationLevel {
51    None,
52    Low,
53    Medium,
54    High,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct CaptureEvent {
59    pub closure_ptr: usize,
60    pub captured_var: CaptureInfo,
61    pub event_type: CaptureEventType,
62    pub timestamp: u64,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub enum CaptureEventType {
67    Captured,
68    Released,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct DetectedClosure {
73    pub ptr: usize,
74    pub type_name: String,
75    pub size: usize,
76    pub estimated_captures: usize,
77    pub closure_type: ClosureType,
78    pub creation_context: CreationContext,
79    pub memory_impact: MemoryImpact,
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub enum ClosureType {
84    Fn,
85    FnMut,
86    FnOnce,
87    Unknown,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct CreationContext {
92    pub scope_name: Option<String>,
93    pub thread_id: String,
94    pub timestamp: u64,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub enum MemoryImpact {
99    Minimal,
100    Low,
101    Medium,
102    High,
103    VeryHigh,
104}
105
106#[derive(Debug, Clone, Default, Serialize, Deserialize)]
107pub struct CaptureStatistics {
108    pub total_closures: usize,
109    pub total_captures: usize,
110    pub avg_captures_per_closure: f64,
111    pub total_memory_usage: usize,
112    pub captures_by_mode: HashMap<CaptureMode, usize>,
113    pub captures_by_type: HashMap<String, usize>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct OptimizationSuggestion {
118    pub category: OptimizationCategory,
119    pub priority: SuggestionPriority,
120    pub description: String,
121    pub recommendation: String,
122    pub estimated_impact: String,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub enum OptimizationCategory {
127    Memory,
128    Performance,
129    Lifetime,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub enum SuggestionPriority {
134    Low,
135    Medium,
136    High,
137    Critical,
138}
139
140#[derive(Debug)]
141pub struct LifetimeGraph {
142    relationships: HashMap<usize, Vec<LifetimeRelationship>>,
143}
144
145impl Default for LifetimeGraph {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl LifetimeGraph {
152    pub fn new() -> Self {
153        Self {
154            relationships: HashMap::new(),
155        }
156    }
157
158    pub fn add_closure_relationships(&mut self, closure_ptr: usize, captures: &[CaptureInfo]) {
159        let relationships: Vec<LifetimeRelationship> = captures
160            .iter()
161            .map(|capture| LifetimeRelationship {
162                captured_var_ptr: capture.var_ptr,
163                capture_mode: capture.mode.clone(),
164                relationship_type: self.classify_relationship(&capture.mode),
165            })
166            .collect();
167
168        self.relationships.insert(closure_ptr, relationships);
169    }
170
171    pub fn remove_closure(&mut self, closure_ptr: usize) {
172        self.relationships.remove(&closure_ptr);
173    }
174
175    fn classify_relationship(&self, mode: &CaptureMode) -> RelationshipType {
176        match mode {
177            CaptureMode::ByValue => RelationshipType::Ownership,
178            CaptureMode::ByReference => RelationshipType::SharedBorrow,
179            CaptureMode::ByMutableReference => RelationshipType::ExclusiveBorrow,
180        }
181    }
182
183    pub fn analyze_lifetimes(&self) -> LifetimeAnalysis {
184        let mut potential_issues = Vec::new();
185        let mut lifetime_patterns = Vec::new();
186
187        for (closure_ptr, relationships) in &self.relationships {
188            let has_value_captures = relationships
189                .iter()
190                .any(|r| r.capture_mode == CaptureMode::ByValue);
191            let has_ref_captures = relationships.iter().any(|r| {
192                matches!(
193                    r.capture_mode,
194                    CaptureMode::ByReference | CaptureMode::ByMutableReference
195                )
196            });
197
198            if has_value_captures && has_ref_captures {
199                potential_issues.push(LifetimeIssue {
200                    closure_ptr: *closure_ptr,
201                    issue_type: LifetimeIssueType::MixedCaptureMode,
202                    description: "Closure mixes value and reference captures".to_string(),
203                    severity: IssueSeverity::Medium,
204                    suggestion: "Consider consistent capture strategy".to_string(),
205                });
206            }
207
208            if relationships.len() > 5 {
209                lifetime_patterns.push(LifetimePattern {
210                    pattern_type: LifetimePatternType::ManyCaptures,
211                    description: format!("Closure captures {} variables", relationships.len()),
212                    impact: if relationships.len() > 10 {
213                        PatternImpact::High
214                    } else {
215                        PatternImpact::Medium
216                    },
217                });
218            }
219        }
220
221        LifetimeAnalysis {
222            total_relationships: self.relationships.len(),
223            potential_issues,
224            lifetime_patterns,
225        }
226    }
227}
228
229#[derive(Debug, Clone)]
230pub struct LifetimeRelationship {
231    pub captured_var_ptr: usize,
232    pub capture_mode: CaptureMode,
233    pub relationship_type: RelationshipType,
234}
235
236#[derive(Debug, Clone, PartialEq)]
237pub enum RelationshipType {
238    Ownership,
239    SharedBorrow,
240    ExclusiveBorrow,
241}
242
243#[derive(Debug, Clone, Default, Serialize, Deserialize)]
244pub struct LifetimeAnalysis {
245    pub total_relationships: usize,
246    pub potential_issues: Vec<LifetimeIssue>,
247    pub lifetime_patterns: Vec<LifetimePattern>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct LifetimeIssue {
252    pub closure_ptr: usize,
253    pub issue_type: LifetimeIssueType,
254    pub description: String,
255    pub severity: IssueSeverity,
256    pub suggestion: String,
257}
258
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub enum LifetimeIssueType {
261    MixedCaptureMode,
262    PotentialDanglingReference,
263    UnnecessaryCapture,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267pub enum IssueSeverity {
268    Low,
269    Medium,
270    High,
271    Critical,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct LifetimePattern {
276    pub pattern_type: LifetimePatternType,
277    pub description: String,
278    pub impact: PatternImpact,
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub enum LifetimePatternType {
283    ManyCaptures,
284    LongLivedClosure,
285    FrequentCreation,
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub enum PatternImpact {
290    Low,
291    Medium,
292    High,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct ClosureAnalysisReport {
297    pub detected_closures: Vec<DetectedClosure>,
298    pub capture_statistics: CaptureStatistics,
299    pub optimization_suggestions: Vec<OptimizationSuggestion>,
300    pub lifetime_analysis: LifetimeAnalysis,
301    pub analysis_timestamp: u64,
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    /// Objective: Verify ClosureInfo creation with all fields
309    /// Invariants: All fields should be properly initialized
310    #[test]
311    fn test_closure_info() {
312        let closure = ClosureInfo {
313            ptr: 0x1000,
314            captures: vec![],
315            creation_timestamp: 1000,
316            thread_id: "main".to_string(),
317            call_site: "test.rs:10".to_string(),
318            memory_footprint: ClosureFootprint {
319                total_size: 64,
320                capture_count: 0,
321                by_value_count: 0,
322                by_ref_count: 0,
323                by_mut_ref_count: 0,
324                estimated_heap_usage: 0,
325            },
326            optimization_potential: OptimizationPotential {
327                level: OptimizationLevel::None,
328                potential_savings: 0,
329                suggestions: vec![],
330            },
331        };
332
333        assert_eq!(closure.ptr, 0x1000, "Pointer should match");
334        assert_eq!(closure.thread_id, "main", "Thread ID should match");
335        assert_eq!(closure.captures.len(), 0, "Should have no captures");
336    }
337
338    /// Objective: Verify CaptureInfo creation
339    /// Invariants: All fields should be accessible
340    #[test]
341    fn test_capture_info() {
342        let capture = CaptureInfo {
343            var_name: "x".to_string(),
344            var_ptr: 0x2000,
345            mode: CaptureMode::ByReference,
346            var_type: "i32".to_string(),
347            size: 4,
348            lifetime_bound: Some("'a".to_string()),
349        };
350
351        assert_eq!(capture.var_name, "x", "Variable name should match");
352        assert_eq!(
353            capture.mode,
354            CaptureMode::ByReference,
355            "Capture mode should be ByReference"
356        );
357        assert_eq!(capture.size, 4, "Size should be 4");
358    }
359
360    /// Objective: Verify CaptureMode variants
361    /// Invariants: All variants should be distinct
362    #[test]
363    fn test_capture_mode_variants() {
364        let modes = vec![
365            CaptureMode::ByValue,
366            CaptureMode::ByReference,
367            CaptureMode::ByMutableReference,
368        ];
369
370        for mode in &modes {
371            let debug_str = format!("{mode:?}");
372            assert!(
373                !debug_str.is_empty(),
374                "CaptureMode should have debug representation"
375            );
376        }
377
378        assert_eq!(CaptureMode::ByValue, CaptureMode::ByValue);
379        assert_ne!(CaptureMode::ByValue, CaptureMode::ByReference);
380    }
381
382    /// Objective: Verify ClosureFootprint creation
383    /// Invariants: All fields should be accessible
384    #[test]
385    fn test_closure_footprint() {
386        let footprint = ClosureFootprint {
387            total_size: 128,
388            capture_count: 5,
389            by_value_count: 2,
390            by_ref_count: 2,
391            by_mut_ref_count: 1,
392            estimated_heap_usage: 64,
393        };
394
395        assert_eq!(footprint.total_size, 128, "Total size should match");
396        assert_eq!(footprint.capture_count, 5, "Capture count should match");
397        assert_eq!(footprint.by_value_count, 2, "By value count should match");
398    }
399
400    /// Objective: Verify OptimizationPotential creation
401    /// Invariants: All fields should be accessible
402    #[test]
403    fn test_optimization_potential() {
404        let potential = OptimizationPotential {
405            level: OptimizationLevel::High,
406            potential_savings: 1024,
407            suggestions: vec!["Use Arc instead of clone".to_string()],
408        };
409
410        assert_eq!(
411            potential.level,
412            OptimizationLevel::High,
413            "Optimization level should be High"
414        );
415        assert_eq!(
416            potential.potential_savings, 1024,
417            "Potential savings should match"
418        );
419        assert_eq!(potential.suggestions.len(), 1, "Should have one suggestion");
420    }
421
422    /// Objective: Verify OptimizationLevel variants
423    /// Invariants: All variants should be distinct
424    #[test]
425    fn test_optimization_level_variants() {
426        assert_eq!(OptimizationLevel::None, OptimizationLevel::None);
427        assert_eq!(OptimizationLevel::Low, OptimizationLevel::Low);
428        assert_eq!(OptimizationLevel::Medium, OptimizationLevel::Medium);
429        assert_eq!(OptimizationLevel::High, OptimizationLevel::High);
430
431        assert_ne!(OptimizationLevel::None, OptimizationLevel::High);
432    }
433
434    /// Objective: Verify CaptureEvent creation
435    /// Invariants: All fields should be accessible
436    #[test]
437    fn test_capture_event() {
438        let event = CaptureEvent {
439            closure_ptr: 0x1000,
440            captured_var: CaptureInfo {
441                var_name: "y".to_string(),
442                var_ptr: 0x2000,
443                mode: CaptureMode::ByValue,
444                var_type: "String".to_string(),
445                size: 24,
446                lifetime_bound: None,
447            },
448            event_type: CaptureEventType::Captured,
449            timestamp: 1000,
450        };
451
452        assert_eq!(
453            event.event_type,
454            CaptureEventType::Captured,
455            "Event type should be Captured"
456        );
457    }
458
459    /// Objective: Verify CaptureEventType variants
460    /// Invariants: All variants should be distinct
461    #[test]
462    fn test_capture_event_type_variants() {
463        assert_eq!(CaptureEventType::Captured, CaptureEventType::Captured);
464        assert_eq!(CaptureEventType::Released, CaptureEventType::Released);
465        assert_ne!(CaptureEventType::Captured, CaptureEventType::Released);
466    }
467
468    /// Objective: Verify DetectedClosure creation
469    /// Invariants: All fields should be accessible
470    #[test]
471    fn test_detected_closure() {
472        let detected = DetectedClosure {
473            ptr: 0x1000,
474            type_name: "closure".to_string(),
475            size: 64,
476            estimated_captures: 3,
477            closure_type: ClosureType::FnMut,
478            creation_context: CreationContext {
479                scope_name: Some("test_scope".to_string()),
480                thread_id: "main".to_string(),
481                timestamp: 1000,
482            },
483            memory_impact: MemoryImpact::Medium,
484        };
485
486        assert_eq!(detected.ptr, 0x1000, "Pointer should match");
487        assert_eq!(
488            detected.closure_type,
489            ClosureType::FnMut,
490            "Closure type should be FnMut"
491        );
492    }
493
494    /// Objective: Verify ClosureType variants
495    /// Invariants: All variants should be distinct
496    #[test]
497    fn test_closure_type_variants() {
498        let types = vec![
499            ClosureType::Fn,
500            ClosureType::FnMut,
501            ClosureType::FnOnce,
502            ClosureType::Unknown,
503        ];
504
505        for closure_type in &types {
506            let debug_str = format!("{closure_type:?}");
507            assert!(
508                !debug_str.is_empty(),
509                "ClosureType should have debug representation"
510            );
511        }
512    }
513
514    /// Objective: Verify MemoryImpact variants
515    /// Invariants: All variants should be distinct
516    #[test]
517    fn test_memory_impact_variants() {
518        let impacts = vec![
519            MemoryImpact::Minimal,
520            MemoryImpact::Low,
521            MemoryImpact::Medium,
522            MemoryImpact::High,
523            MemoryImpact::VeryHigh,
524        ];
525
526        for impact in &impacts {
527            let debug_str = format!("{impact:?}");
528            assert!(
529                !debug_str.is_empty(),
530                "MemoryImpact should have debug representation"
531            );
532        }
533    }
534
535    /// Objective: Verify CaptureStatistics creation
536    /// Invariants: Default should have zero values
537    #[test]
538    fn test_capture_statistics_default() {
539        let stats = CaptureStatistics::default();
540
541        assert_eq!(stats.total_closures, 0, "Total closures should be 0");
542        assert_eq!(stats.total_captures, 0, "Total captures should be 0");
543        assert_eq!(stats.avg_captures_per_closure, 0.0, "Average should be 0.0");
544    }
545
546    /// Objective: Verify CaptureStatistics with values
547    /// Invariants: Should handle populated statistics
548    #[test]
549    fn test_capture_statistics_with_values() {
550        let mut captures_by_mode = HashMap::new();
551        captures_by_mode.insert(CaptureMode::ByValue, 10);
552        captures_by_mode.insert(CaptureMode::ByReference, 20);
553
554        let stats = CaptureStatistics {
555            total_closures: 5,
556            total_captures: 30,
557            avg_captures_per_closure: 6.0,
558            total_memory_usage: 1024,
559            captures_by_mode,
560            captures_by_type: HashMap::new(),
561        };
562
563        assert_eq!(stats.total_closures, 5, "Total closures should be 5");
564        assert_eq!(
565            stats.captures_by_mode.get(&CaptureMode::ByValue),
566            Some(&10),
567            "ByValue count should be 10"
568        );
569    }
570
571    /// Objective: Verify OptimizationSuggestion creation
572    /// Invariants: All fields should be accessible
573    #[test]
574    fn test_optimization_suggestion() {
575        let suggestion = OptimizationSuggestion {
576            category: OptimizationCategory::Memory,
577            priority: SuggestionPriority::High,
578            description: "Large closure detected".to_string(),
579            recommendation: "Consider using Arc".to_string(),
580            estimated_impact: "Save 1KB".to_string(),
581        };
582
583        assert_eq!(
584            suggestion.category,
585            OptimizationCategory::Memory,
586            "Category should be Memory"
587        );
588        assert_eq!(
589            suggestion.priority,
590            SuggestionPriority::High,
591            "Priority should be High"
592        );
593    }
594
595    /// Objective: Verify OptimizationCategory variants
596    /// Invariants: All variants should be distinct
597    #[test]
598    fn test_optimization_category_variants() {
599        assert_eq!(OptimizationCategory::Memory, OptimizationCategory::Memory);
600        assert_eq!(
601            OptimizationCategory::Performance,
602            OptimizationCategory::Performance
603        );
604        assert_eq!(
605            OptimizationCategory::Lifetime,
606            OptimizationCategory::Lifetime
607        );
608    }
609
610    /// Objective: Verify SuggestionPriority variants
611    /// Invariants: All variants should be distinct
612    #[test]
613    fn test_suggestion_priority_variants() {
614        assert_eq!(SuggestionPriority::Low, SuggestionPriority::Low);
615        assert_eq!(SuggestionPriority::Medium, SuggestionPriority::Medium);
616        assert_eq!(SuggestionPriority::High, SuggestionPriority::High);
617        assert_eq!(SuggestionPriority::Critical, SuggestionPriority::Critical);
618    }
619
620    /// Objective: Verify LifetimeGraph creation
621    /// Invariants: New graph should be empty
622    #[test]
623    fn test_lifetime_graph_creation() {
624        let graph = LifetimeGraph::new();
625        assert_eq!(
626            graph.relationships.len(),
627            0,
628            "New graph should have no relationships"
629        );
630    }
631
632    /// Objective: Verify LifetimeGraph default
633    /// Invariants: Default should create same as new()
634    #[test]
635    fn test_lifetime_graph_default() {
636        let graph = LifetimeGraph::default();
637        assert_eq!(
638            graph.relationships.len(),
639            0,
640            "Default graph should have no relationships"
641        );
642    }
643
644    /// Objective: Verify add_closure_relationships functionality
645    /// Invariants: Should add relationships correctly
646    #[test]
647    fn test_lifetime_graph_add_relationships() {
648        let mut graph = LifetimeGraph::new();
649
650        let captures = vec![
651            CaptureInfo {
652                var_name: "x".to_string(),
653                var_ptr: 0x1000,
654                mode: CaptureMode::ByValue,
655                var_type: "i32".to_string(),
656                size: 4,
657                lifetime_bound: None,
658            },
659            CaptureInfo {
660                var_name: "y".to_string(),
661                var_ptr: 0x2000,
662                mode: CaptureMode::ByReference,
663                var_type: "String".to_string(),
664                size: 24,
665                lifetime_bound: None,
666            },
667        ];
668
669        graph.add_closure_relationships(0x5000, &captures);
670
671        assert_eq!(
672            graph.relationships.len(),
673            1,
674            "Should have one closure relationship"
675        );
676        assert_eq!(
677            graph.relationships.get(&0x5000).unwrap().len(),
678            2,
679            "Should have two capture relationships"
680        );
681    }
682
683    /// Objective: Verify remove_closure functionality
684    /// Invariants: Should remove relationships correctly
685    #[test]
686    fn test_lifetime_graph_remove_closure() {
687        let mut graph = LifetimeGraph::new();
688
689        let captures = vec![CaptureInfo {
690            var_name: "x".to_string(),
691            var_ptr: 0x1000,
692            mode: CaptureMode::ByValue,
693            var_type: "i32".to_string(),
694            size: 4,
695            lifetime_bound: None,
696        }];
697
698        graph.add_closure_relationships(0x5000, &captures);
699        assert_eq!(graph.relationships.len(), 1, "Should have one relationship");
700
701        graph.remove_closure(0x5000);
702        assert_eq!(
703            graph.relationships.len(),
704            0,
705            "Should have no relationships after removal"
706        );
707    }
708
709    /// Objective: Verify analyze_lifetimes with mixed captures
710    /// Invariants: Should detect mixed capture mode issue
711    #[test]
712    fn test_lifetime_graph_analyze_mixed_captures() {
713        let mut graph = LifetimeGraph::new();
714
715        let captures = vec![
716            CaptureInfo {
717                var_name: "x".to_string(),
718                var_ptr: 0x1000,
719                mode: CaptureMode::ByValue,
720                var_type: "i32".to_string(),
721                size: 4,
722                lifetime_bound: None,
723            },
724            CaptureInfo {
725                var_name: "y".to_string(),
726                var_ptr: 0x2000,
727                mode: CaptureMode::ByReference,
728                var_type: "String".to_string(),
729                size: 24,
730                lifetime_bound: None,
731            },
732        ];
733
734        graph.add_closure_relationships(0x5000, &captures);
735        let analysis = graph.analyze_lifetimes();
736
737        assert_eq!(
738            analysis.total_relationships, 1,
739            "Should have one relationship"
740        );
741        assert!(
742            analysis
743                .potential_issues
744                .iter()
745                .any(|i| i.issue_type == LifetimeIssueType::MixedCaptureMode),
746            "Should detect mixed capture mode issue"
747        );
748    }
749
750    /// Objective: Verify analyze_lifetimes with many captures
751    /// Invariants: Should detect many captures pattern
752    #[test]
753    fn test_lifetime_graph_analyze_many_captures() {
754        let mut graph = LifetimeGraph::new();
755
756        let captures: Vec<CaptureInfo> = (0..8)
757            .map(|i| CaptureInfo {
758                var_name: format!("var{}", i),
759                var_ptr: 0x1000 + i,
760                mode: CaptureMode::ByReference,
761                var_type: "i32".to_string(),
762                size: 4,
763                lifetime_bound: None,
764            })
765            .collect();
766
767        graph.add_closure_relationships(0x5000, &captures);
768        let analysis = graph.analyze_lifetimes();
769
770        assert!(
771            analysis
772                .lifetime_patterns
773                .iter()
774                .any(|p| p.pattern_type == LifetimePatternType::ManyCaptures),
775            "Should detect many captures pattern"
776        );
777    }
778
779    /// Objective: Verify RelationshipType classification
780    /// Invariants: Each capture mode should map to correct relationship type
781    #[test]
782    fn test_relationship_type_classification() {
783        let captures = vec![
784            CaptureInfo {
785                var_name: "v1".to_string(),
786                var_ptr: 0x1000,
787                mode: CaptureMode::ByValue,
788                var_type: "i32".to_string(),
789                size: 4,
790                lifetime_bound: None,
791            },
792            CaptureInfo {
793                var_name: "v2".to_string(),
794                var_ptr: 0x2000,
795                mode: CaptureMode::ByReference,
796                var_type: "i32".to_string(),
797                size: 4,
798                lifetime_bound: None,
799            },
800            CaptureInfo {
801                var_name: "v3".to_string(),
802                var_ptr: 0x3000,
803                mode: CaptureMode::ByMutableReference,
804                var_type: "i32".to_string(),
805                size: 4,
806                lifetime_bound: None,
807            },
808        ];
809
810        let mut test_graph = LifetimeGraph::new();
811        test_graph.add_closure_relationships(0x5000, &captures);
812
813        let rels = test_graph.relationships.get(&0x5000).unwrap();
814        assert_eq!(
815            rels[0].relationship_type,
816            RelationshipType::Ownership,
817            "ByValue should be Ownership"
818        );
819        assert_eq!(
820            rels[1].relationship_type,
821            RelationshipType::SharedBorrow,
822            "ByReference should be SharedBorrow"
823        );
824        assert_eq!(
825            rels[2].relationship_type,
826            RelationshipType::ExclusiveBorrow,
827            "ByMutableReference should be ExclusiveBorrow"
828        );
829    }
830
831    /// Objective: Verify LifetimeIssue creation
832    /// Invariants: All fields should be accessible
833    #[test]
834    fn test_lifetime_issue() {
835        let issue = LifetimeIssue {
836            closure_ptr: 0x1000,
837            issue_type: LifetimeIssueType::PotentialDanglingReference,
838            description: "Potential dangling reference".to_string(),
839            severity: IssueSeverity::High,
840            suggestion: "Check lifetime bounds".to_string(),
841        };
842
843        assert_eq!(
844            issue.issue_type,
845            LifetimeIssueType::PotentialDanglingReference,
846            "Issue type should match"
847        );
848        assert_eq!(
849            issue.severity,
850            IssueSeverity::High,
851            "Severity should be High"
852        );
853    }
854
855    /// Objective: Verify LifetimePattern creation
856    /// Invariants: All fields should be accessible
857    #[test]
858    fn test_lifetime_pattern() {
859        let pattern = LifetimePattern {
860            pattern_type: LifetimePatternType::LongLivedClosure,
861            description: "Closure lives for entire program".to_string(),
862            impact: PatternImpact::High,
863        };
864
865        assert_eq!(
866            pattern.pattern_type,
867            LifetimePatternType::LongLivedClosure,
868            "Pattern type should match"
869        );
870        assert_eq!(pattern.impact, PatternImpact::High, "Impact should be High");
871    }
872
873    /// Objective: Verify ClosureAnalysisReport creation
874    /// Invariants: All fields should be properly initialized
875    #[test]
876    fn test_closure_analysis_report() {
877        let report = ClosureAnalysisReport {
878            detected_closures: vec![],
879            capture_statistics: CaptureStatistics::default(),
880            optimization_suggestions: vec![],
881            lifetime_analysis: LifetimeAnalysis::default(),
882            analysis_timestamp: 1000,
883        };
884
885        assert_eq!(
886            report.detected_closures.len(),
887            0,
888            "Should have no detected closures"
889        );
890        assert_eq!(report.analysis_timestamp, 1000, "Timestamp should match");
891    }
892
893    /// Objective: Verify serialization of CaptureMode
894    /// Invariants: Should serialize and deserialize correctly
895    #[test]
896    fn test_capture_mode_serialization() {
897        let mode = CaptureMode::ByMutableReference;
898        let json = serde_json::to_string(&mode);
899        assert!(json.is_ok(), "Should serialize to JSON");
900
901        let deserialized: Result<CaptureMode, _> = serde_json::from_str(&json.unwrap());
902        assert!(deserialized.is_ok(), "Should deserialize from JSON");
903        assert_eq!(
904            deserialized.unwrap(),
905            CaptureMode::ByMutableReference,
906            "Should preserve value"
907        );
908    }
909
910    /// Objective: Verify serialization of OptimizationLevel
911    /// Invariants: Should serialize and deserialize correctly
912    #[test]
913    fn test_optimization_level_serialization() {
914        let level = OptimizationLevel::Medium;
915        let json = serde_json::to_string(&level);
916        assert!(json.is_ok(), "Should serialize to JSON");
917
918        let deserialized: Result<OptimizationLevel, _> = serde_json::from_str(&json.unwrap());
919        assert!(deserialized.is_ok(), "Should deserialize from JSON");
920        assert_eq!(
921            deserialized.unwrap(),
922            OptimizationLevel::Medium,
923            "Should preserve value"
924        );
925    }
926}