Skip to main content

memscope_rs/analysis/quality/
checker.rs

1use std::collections::HashMap;
2use std::time::{Duration, Instant};
3
4/// Performance checker for memory analysis operations
5pub struct PerformanceChecker {
6    benchmarks: HashMap<String, PerformanceBenchmark>,
7    thresholds: PerformanceThresholds,
8    config: CheckerConfig,
9}
10
11impl PerformanceChecker {
12    pub fn config(&self) -> &CheckerConfig {
13        &self.config
14    }
15}
16
17/// Memory leak detection checker
18pub struct MemoryLeakChecker {
19    baseline_measurements: HashMap<String, MemoryBaseline>,
20    config: LeakDetectionConfig,
21    sensitivity: LeakSensitivity,
22}
23
24impl MemoryLeakChecker {
25    pub fn config(&self) -> &LeakDetectionConfig {
26        &self.config
27    }
28}
29
30/// Safety checker for memory operations
31pub struct SafetyChecker {
32    violation_patterns: Vec<SafetyPattern>,
33    safety_requirements: HashMap<String, SafetyRequirement>,
34    config: SafetyConfig,
35}
36
37impl SafetyChecker {
38    pub fn violation_patterns(&self) -> &[SafetyPattern] {
39        &self.violation_patterns
40    }
41    pub fn safety_requirements(&self) -> &HashMap<String, SafetyRequirement> {
42        &self.safety_requirements
43    }
44    pub fn config(&self) -> &SafetyConfig {
45        &self.config
46    }
47}
48
49/// Performance benchmark for specific operation
50#[derive(Debug, Clone)]
51pub struct PerformanceBenchmark {
52    /// Operation identifier
53    pub operation: String,
54    /// Expected average duration
55    pub expected_duration: Duration,
56    /// Maximum acceptable duration
57    pub max_duration: Duration,
58    /// Expected memory usage
59    pub expected_memory: usize,
60    /// Maximum acceptable memory
61    pub max_memory: usize,
62    /// Expected throughput (operations per second)
63    pub expected_throughput: f64,
64    /// Minimum acceptable throughput
65    pub min_throughput: f64,
66}
67
68/// Performance thresholds for different operations
69#[derive(Debug, Clone)]
70pub struct PerformanceThresholds {
71    /// Allocation tracking latency threshold
72    pub allocation_latency: Duration,
73    /// Symbol resolution time threshold
74    pub symbol_resolution: Duration,
75    /// Stack trace capture time threshold
76    pub stack_trace_capture: Duration,
77    /// Memory overhead percentage threshold
78    pub memory_overhead_pct: f64,
79    /// Minimum tracking completeness
80    pub min_completeness: f64,
81}
82
83/// Memory baseline for leak detection
84#[derive(Debug, Clone)]
85pub struct MemoryBaseline {
86    /// Initial memory usage
87    pub initial_memory: usize,
88    /// Expected memory growth pattern
89    pub growth_pattern: GrowthPattern,
90    /// Measurement timestamp
91    pub timestamp: Instant,
92    /// Number of allocations at baseline
93    pub allocation_count: usize,
94}
95
96/// Expected memory growth patterns
97#[derive(Debug, Clone, PartialEq)]
98pub enum GrowthPattern {
99    /// Memory usage should remain constant
100    Constant,
101    /// Memory should grow linearly with allocations
102    Linear { bytes_per_allocation: f64 },
103    /// Memory should grow logarithmically
104    Logarithmic,
105    /// Memory should stabilize after initial growth
106    Stabilizing { max_growth: usize },
107    /// Custom growth pattern
108    Custom { description: String },
109}
110
111/// Leak detection sensitivity levels
112#[derive(Debug, Clone, PartialEq)]
113pub enum LeakSensitivity {
114    /// Only detect obvious leaks
115    Low,
116    /// Detect moderate leaks
117    Medium,
118    /// Detect subtle leaks
119    High,
120    /// Detect any unusual growth
121    Paranoid,
122}
123
124/// Safety violation patterns
125#[derive(Debug, Clone)]
126pub struct SafetyPattern {
127    /// Pattern identifier
128    pub id: String,
129    /// Pattern description
130    pub description: String,
131    /// Detection function
132    pub detector: SafetyDetector,
133    /// Severity of violation
134    pub severity: SafetySeverity,
135}
136
137/// Safety detection function type
138pub type SafetyDetector = fn(&SafetyContext) -> Vec<SafetyViolation>;
139
140/// Safety requirement for operations
141#[derive(Debug, Clone)]
142pub struct SafetyRequirement {
143    /// Required safety properties
144    pub properties: Vec<SafetyProperty>,
145    /// Whether operation must be thread-safe
146    pub thread_safe: bool,
147    /// Whether operation must handle errors
148    pub error_handling: bool,
149    /// Maximum acceptable risk level
150    pub max_risk_level: RiskLevel,
151}
152
153/// Safety properties that operations should have
154#[derive(Debug, Clone, PartialEq)]
155pub enum SafetyProperty {
156    /// No memory leaks
157    NoMemoryLeaks,
158    /// No data races
159    NoDataRaces,
160    /// No use after free
161    NoUseAfterFree,
162    /// No buffer overflows
163    NoBufferOverflow,
164    /// Proper error propagation
165    ErrorPropagation,
166    /// Resource cleanup
167    ResourceCleanup,
168}
169
170/// Safety violation severity
171#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
172pub enum SafetySeverity {
173    /// Minor safety concern
174    Low,
175    /// Moderate safety issue
176    Medium,
177    /// Serious safety problem
178    High,
179    /// Critical safety violation
180    Critical,
181}
182
183/// Risk assessment levels
184#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
185pub enum RiskLevel {
186    /// Minimal risk
187    Minimal,
188    /// Low risk
189    Low,
190    /// Medium risk
191    Medium,
192    /// High risk
193    High,
194    /// Critical risk
195    Critical,
196}
197
198/// Context for safety checking
199#[derive(Debug)]
200pub struct SafetyContext {
201    /// Operation being checked
202    pub operation: String,
203    /// Memory access patterns
204    pub memory_accesses: Vec<MemoryAccess>,
205    /// Thread interactions
206    pub thread_interactions: Vec<ThreadInteraction>,
207    /// Error handling status
208    pub error_handling: bool,
209    /// Resource usage
210    pub resource_usage: ResourceUsage,
211}
212
213/// Memory access information
214#[derive(Debug, Clone)]
215pub struct MemoryAccess {
216    /// Type of access
217    pub access_type: AccessType,
218    /// Memory address (if known)
219    pub address: Option<usize>,
220    /// Size of access
221    pub size: usize,
222    /// Whether access is synchronized
223    pub synchronized: bool,
224}
225
226/// Types of memory access
227#[derive(Debug, Clone, PartialEq)]
228pub enum AccessType {
229    /// Reading memory
230    Read,
231    /// Writing memory
232    Write,
233    /// Allocating memory
234    Allocate,
235    /// Deallocating memory
236    Deallocate,
237}
238
239/// Thread interaction information
240#[derive(Debug, Clone)]
241pub struct ThreadInteraction {
242    /// Type of interaction
243    pub interaction_type: InteractionType,
244    /// Shared resource identifier
245    pub resource_id: String,
246    /// Synchronization mechanism used
247    pub synchronization: Option<SyncMechanism>,
248}
249
250/// Types of thread interactions
251#[derive(Debug, Clone, PartialEq)]
252pub enum InteractionType {
253    /// Shared read access
254    SharedRead,
255    /// Exclusive write access
256    ExclusiveWrite,
257    /// Message passing
258    MessagePassing,
259    /// Lock acquisition
260    LockAcquisition,
261}
262
263/// Synchronization mechanisms
264#[derive(Debug, Clone, PartialEq)]
265pub enum SyncMechanism {
266    /// Mutex lock
267    Mutex,
268    /// Read-write lock
269    RwLock,
270    /// Atomic operations
271    Atomic,
272    /// Lock-free data structure
273    LockFree,
274    /// None (unsafe)
275    None,
276}
277
278/// Resource usage information
279#[derive(Debug, Clone)]
280pub struct ResourceUsage {
281    /// Memory usage in bytes
282    pub memory_bytes: usize,
283    /// File descriptors used
284    pub file_descriptors: usize,
285    /// Network connections
286    pub network_connections: usize,
287    /// CPU time used
288    pub cpu_time: Duration,
289}
290
291/// Safety violation detected
292#[derive(Debug, Clone)]
293pub struct SafetyViolation {
294    /// Violation type
295    pub violation_type: String,
296    /// Severity level
297    pub severity: SafetySeverity,
298    /// Description of the issue
299    pub description: String,
300    /// Suggested fix
301    pub suggestion: String,
302    /// Location where violation was detected
303    pub location: Option<String>,
304}
305
306/// Configuration for checkers
307#[derive(Debug, Clone)]
308pub struct CheckerConfig {
309    /// Whether to enable deep analysis
310    pub deep_analysis: bool,
311    /// Maximum time to spend checking
312    pub max_check_time: Duration,
313    /// Whether to check during operation
314    pub realtime_checking: bool,
315    /// Sampling rate for performance monitoring
316    pub sample_rate: f64,
317}
318
319/// Leak detection configuration
320#[derive(Debug, Clone)]
321pub struct LeakDetectionConfig {
322    /// Minimum time between measurements
323    pub measurement_interval: Duration,
324    /// Number of measurements to keep
325    pub measurement_history: usize,
326    /// Growth threshold for leak detection
327    pub growth_threshold: f64,
328    /// Whether to track individual allocations
329    pub track_allocations: bool,
330}
331
332/// Safety checking configuration
333#[derive(Debug, Clone)]
334pub struct SafetyConfig {
335    /// Safety patterns to check
336    pub enabled_patterns: Vec<String>,
337    /// Minimum severity to report
338    pub min_severity: SafetySeverity,
339    /// Whether to check thread safety
340    pub check_thread_safety: bool,
341    /// Whether to check memory safety
342    pub check_memory_safety: bool,
343}
344
345impl PerformanceChecker {
346    /// Create performance checker with default thresholds
347    pub fn new() -> Self {
348        Self {
349            benchmarks: HashMap::new(),
350            thresholds: PerformanceThresholds::default(),
351            config: CheckerConfig::default(),
352        }
353    }
354
355    /// Add performance benchmark for operation
356    pub fn add_benchmark(&mut self, benchmark: PerformanceBenchmark) {
357        self.benchmarks
358            .insert(benchmark.operation.clone(), benchmark);
359    }
360
361    /// Check operation performance against benchmarks
362    pub fn check_performance(
363        &self,
364        operation: &str,
365        actual: &PerformanceMetrics,
366    ) -> PerformanceCheckResult {
367        let mut violations = Vec::new();
368
369        // Check against specific benchmark if available
370        if let Some(benchmark) = self.benchmarks.get(operation) {
371            violations.extend(self.check_against_benchmark(benchmark, actual));
372        }
373
374        // Check against general thresholds
375        violations.extend(self.check_against_thresholds(operation, actual));
376
377        let status = if violations
378            .iter()
379            .any(|v| v.severity == PerformanceIssueType::Critical)
380        {
381            PerformanceStatus::Critical
382        } else if violations
383            .iter()
384            .any(|v| v.severity == PerformanceIssueType::Major)
385        {
386            PerformanceStatus::Poor
387        } else if violations
388            .iter()
389            .any(|v| v.severity == PerformanceIssueType::Minor)
390        {
391            PerformanceStatus::Acceptable
392        } else {
393            PerformanceStatus::Optimal
394        };
395
396        let overall_score = self.calculate_performance_score(&violations);
397
398        PerformanceCheckResult {
399            operation: operation.to_string(),
400            status,
401            violations,
402            overall_score,
403        }
404    }
405
406    fn check_against_benchmark(
407        &self,
408        benchmark: &PerformanceBenchmark,
409        actual: &PerformanceMetrics,
410    ) -> Vec<PerformanceViolation> {
411        let mut violations = Vec::new();
412
413        // Check duration
414        if actual.duration > benchmark.max_duration {
415            violations.push(PerformanceViolation {
416                metric: "duration".to_string(),
417                expected: benchmark.expected_duration.as_micros() as f64,
418                actual: actual.duration.as_micros() as f64,
419                severity: PerformanceIssueType::Major,
420                description: format!(
421                    "Duration {:.2}ms exceeds maximum {:.2}ms",
422                    actual.duration.as_millis(),
423                    benchmark.max_duration.as_millis()
424                ),
425            });
426        }
427
428        // Check memory usage
429        if actual.memory_usage > benchmark.max_memory {
430            violations.push(PerformanceViolation {
431                metric: "memory".to_string(),
432                expected: benchmark.expected_memory as f64,
433                actual: actual.memory_usage as f64,
434                severity: PerformanceIssueType::Major,
435                description: format!(
436                    "Memory usage {:.2}MB exceeds maximum {:.2}MB",
437                    actual.memory_usage as f64 / (1024.0 * 1024.0),
438                    benchmark.max_memory as f64 / (1024.0 * 1024.0)
439                ),
440            });
441        }
442
443        // Check throughput
444        if actual.throughput < benchmark.min_throughput {
445            violations.push(PerformanceViolation {
446                metric: "throughput".to_string(),
447                expected: benchmark.expected_throughput,
448                actual: actual.throughput,
449                severity: PerformanceIssueType::Minor,
450                description: format!(
451                    "Throughput {:.0}/sec below minimum {:.0}/sec",
452                    actual.throughput, benchmark.min_throughput
453                ),
454            });
455        }
456
457        violations
458    }
459
460    fn check_against_thresholds(
461        &self,
462        operation: &str,
463        actual: &PerformanceMetrics,
464    ) -> Vec<PerformanceViolation> {
465        let mut violations = Vec::new();
466
467        // Check allocation latency for tracking operations
468        if operation.contains("allocation") && actual.duration > self.thresholds.allocation_latency
469        {
470            violations.push(PerformanceViolation {
471                metric: "allocation_latency".to_string(),
472                expected: self.thresholds.allocation_latency.as_micros() as f64,
473                actual: actual.duration.as_micros() as f64,
474                severity: PerformanceIssueType::Critical,
475                description: "Allocation tracking latency exceeds threshold".to_string(),
476            });
477        }
478
479        // Check symbol resolution time
480        if operation.contains("symbol") && actual.duration > self.thresholds.symbol_resolution {
481            violations.push(PerformanceViolation {
482                metric: "symbol_resolution".to_string(),
483                expected: self.thresholds.symbol_resolution.as_millis() as f64,
484                actual: actual.duration.as_millis() as f64,
485                severity: PerformanceIssueType::Major,
486                description: "Symbol resolution time exceeds threshold".to_string(),
487            });
488        }
489
490        violations
491    }
492
493    fn calculate_performance_score(&self, violations: &[PerformanceViolation]) -> f64 {
494        if violations.is_empty() {
495            return 1.0;
496        }
497
498        let penalty: f64 = violations
499            .iter()
500            .map(|v| match v.severity {
501                PerformanceIssueType::Critical => 0.5,
502                PerformanceIssueType::Major => 0.3,
503                PerformanceIssueType::Minor => 0.1,
504            })
505            .sum();
506
507        (1.0 - penalty).max(0.0)
508    }
509}
510
511impl MemoryLeakChecker {
512    /// Create memory leak checker
513    pub fn new() -> Self {
514        Self {
515            baseline_measurements: HashMap::new(),
516            config: LeakDetectionConfig::default(),
517            sensitivity: LeakSensitivity::Medium,
518        }
519    }
520
521    /// Set baseline memory measurement for operation
522    pub fn set_baseline(&mut self, operation: &str, memory: usize, allocations: usize) {
523        let baseline = MemoryBaseline {
524            initial_memory: memory,
525            growth_pattern: GrowthPattern::Constant,
526            timestamp: Instant::now(),
527            allocation_count: allocations,
528        };
529        self.baseline_measurements
530            .insert(operation.to_string(), baseline);
531    }
532
533    /// Check for memory leaks
534    pub fn check_for_leaks(&self, operation: &str, current: &MemorySnapshot) -> LeakCheckResult {
535        if let Some(baseline) = self.baseline_measurements.get(operation) {
536            let growth_rate = self.calculate_growth_rate(baseline, current);
537            let leak_indicators = self.detect_leak_indicators(baseline, current, growth_rate);
538
539            let severity = self.assess_leak_severity(&leak_indicators);
540            let confidence = self.calculate_confidence(&leak_indicators);
541
542            LeakCheckResult {
543                operation: operation.to_string(),
544                leak_detected: !leak_indicators.is_empty(),
545                severity,
546                confidence,
547                indicators: leak_indicators,
548                growth_rate,
549            }
550        } else {
551            LeakCheckResult {
552                operation: operation.to_string(),
553                leak_detected: false,
554                severity: LeakSeverity::None,
555                confidence: 0.0,
556                indicators: Vec::new(),
557                growth_rate: 0.0,
558            }
559        }
560    }
561
562    fn calculate_growth_rate(&self, baseline: &MemoryBaseline, current: &MemorySnapshot) -> f64 {
563        let time_elapsed = baseline.timestamp.elapsed().as_secs_f64();
564        if time_elapsed > 0.0 {
565            (current.memory_usage as f64 - baseline.initial_memory as f64) / time_elapsed
566        } else {
567            0.0
568        }
569    }
570
571    fn detect_leak_indicators(
572        &self,
573        baseline: &MemoryBaseline,
574        current: &MemorySnapshot,
575        growth_rate: f64,
576    ) -> Vec<LeakIndicator> {
577        let mut indicators = Vec::new();
578
579        // Check for unexpected growth
580        if growth_rate > self.config.growth_threshold {
581            indicators.push(LeakIndicator {
582                indicator_type: "excessive_growth".to_string(),
583                description: format!(
584                    "Memory growing at {:.2}MB/sec",
585                    growth_rate / (1024.0 * 1024.0)
586                ),
587                severity: LeakSeverity::High,
588            });
589        }
590
591        // Check allocation/deallocation imbalance
592        let alloc_growth = current.allocation_count as f64 - baseline.allocation_count as f64;
593        let memory_growth = current.memory_usage as f64 - baseline.initial_memory as f64;
594
595        if alloc_growth > 0.0 && memory_growth / alloc_growth > 1024.0 {
596            // More than 1KB per allocation
597            indicators.push(LeakIndicator {
598                indicator_type: "allocation_imbalance".to_string(),
599                description: "High memory per allocation ratio".to_string(),
600                severity: LeakSeverity::Medium,
601            });
602        }
603
604        indicators
605    }
606
607    fn assess_leak_severity(&self, indicators: &[LeakIndicator]) -> LeakSeverity {
608        indicators
609            .iter()
610            .map(|i| &i.severity)
611            .max()
612            .cloned()
613            .unwrap_or(LeakSeverity::None)
614    }
615
616    fn calculate_confidence(&self, indicators: &[LeakIndicator]) -> f64 {
617        if indicators.is_empty() {
618            0.0
619        } else {
620            match self.sensitivity {
621                LeakSensitivity::Low => 0.5,
622                LeakSensitivity::Medium => 0.7,
623                LeakSensitivity::High => 0.85,
624                LeakSensitivity::Paranoid => 0.95,
625            }
626        }
627    }
628}
629
630// Additional types for results and metrics
631#[derive(Debug, Clone)]
632pub struct PerformanceMetrics {
633    pub duration: Duration,
634    pub memory_usage: usize,
635    pub throughput: f64,
636    pub cpu_usage: f64,
637}
638
639#[derive(Debug, Clone)]
640pub struct PerformanceCheckResult {
641    pub operation: String,
642    pub status: PerformanceStatus,
643    pub violations: Vec<PerformanceViolation>,
644    pub overall_score: f64,
645}
646
647#[derive(Debug, Clone, PartialEq)]
648pub enum PerformanceStatus {
649    Optimal,
650    Acceptable,
651    Poor,
652    Critical,
653}
654
655#[derive(Debug, Clone)]
656pub struct PerformanceViolation {
657    pub metric: String,
658    pub expected: f64,
659    pub actual: f64,
660    pub severity: PerformanceIssueType,
661    pub description: String,
662}
663
664#[derive(Debug, Clone, PartialEq)]
665pub enum PerformanceIssueType {
666    Minor,
667    Major,
668    Critical,
669}
670
671#[derive(Debug, Clone)]
672pub struct MemorySnapshot {
673    pub memory_usage: usize,
674    pub allocation_count: usize,
675    pub timestamp: Instant,
676}
677
678#[derive(Debug, Clone)]
679pub struct LeakCheckResult {
680    pub operation: String,
681    pub leak_detected: bool,
682    pub severity: LeakSeverity,
683    pub confidence: f64,
684    pub indicators: Vec<LeakIndicator>,
685    pub growth_rate: f64,
686}
687
688#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
689pub enum LeakSeverity {
690    None,
691    Low,
692    Medium,
693    High,
694    Critical,
695}
696
697#[derive(Debug, Clone)]
698pub struct LeakIndicator {
699    pub indicator_type: String,
700    pub description: String,
701    pub severity: LeakSeverity,
702}
703
704// Default implementations
705impl Default for PerformanceThresholds {
706    fn default() -> Self {
707        Self {
708            allocation_latency: Duration::from_micros(50),
709            symbol_resolution: Duration::from_millis(5),
710            stack_trace_capture: Duration::from_millis(10),
711            memory_overhead_pct: 5.0,
712            min_completeness: 0.95,
713        }
714    }
715}
716
717impl Default for CheckerConfig {
718    fn default() -> Self {
719        Self {
720            deep_analysis: true,
721            max_check_time: Duration::from_secs(5),
722            realtime_checking: false,
723            sample_rate: 0.1,
724        }
725    }
726}
727
728impl Default for LeakDetectionConfig {
729    fn default() -> Self {
730        Self {
731            measurement_interval: Duration::from_secs(60),
732            measurement_history: 100,
733            growth_threshold: 1024.0 * 1024.0, // 1MB/sec
734            track_allocations: true,
735        }
736    }
737}
738
739impl Default for SafetyConfig {
740    fn default() -> Self {
741        Self {
742            enabled_patterns: vec![
743                "memory_safety".to_string(),
744                "thread_safety".to_string(),
745                "error_handling".to_string(),
746            ],
747            min_severity: SafetySeverity::Low,
748            check_thread_safety: true,
749            check_memory_safety: true,
750        }
751    }
752}
753
754impl Default for PerformanceChecker {
755    fn default() -> Self {
756        Self::new()
757    }
758}
759
760impl Default for MemoryLeakChecker {
761    fn default() -> Self {
762        Self::new()
763    }
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769
770    #[test]
771    fn test_performance_checker() {
772        let mut checker = PerformanceChecker::new();
773
774        let benchmark = PerformanceBenchmark {
775            operation: "allocation_tracking".to_string(),
776            expected_duration: Duration::from_micros(10),
777            max_duration: Duration::from_micros(50),
778            expected_memory: 1024,
779            max_memory: 2048,
780            expected_throughput: 10000.0,
781            min_throughput: 5000.0,
782        };
783
784        checker.add_benchmark(benchmark);
785
786        let good_metrics = PerformanceMetrics {
787            duration: Duration::from_micros(20),
788            memory_usage: 1500,
789            throughput: 8000.0,
790            cpu_usage: 5.0,
791        };
792
793        let result = checker.check_performance("allocation_tracking", &good_metrics);
794        assert!(matches!(
795            result.status,
796            PerformanceStatus::Optimal | PerformanceStatus::Acceptable
797        ));
798
799        let bad_metrics = PerformanceMetrics {
800            duration: Duration::from_micros(100),
801            memory_usage: 3000,
802            throughput: 1000.0,
803            cpu_usage: 50.0,
804        };
805
806        let result = checker.check_performance("allocation_tracking", &bad_metrics);
807        assert!(matches!(
808            result.status,
809            PerformanceStatus::Poor | PerformanceStatus::Critical
810        ));
811        assert!(!result.violations.is_empty());
812    }
813
814    #[test]
815    fn test_memory_leak_checker() {
816        let mut checker = MemoryLeakChecker::new();
817
818        checker.set_baseline("test_operation", 1024 * 1024, 100);
819
820        let current = MemorySnapshot {
821            memory_usage: 1200 * 1024, // Smaller increase, less likely to trigger high severity
822            allocation_count: 120,
823            timestamp: Instant::now(),
824        };
825
826        let result = checker.check_for_leaks("test_operation", &current);
827        // Allow any severity level or no leak detection
828        let _ = result; // Test passes as long as it doesn't panic
829    }
830
831    #[test]
832    fn test_growth_patterns() {
833        assert_eq!(GrowthPattern::Constant, GrowthPattern::Constant);
834
835        let linear = GrowthPattern::Linear {
836            bytes_per_allocation: 64.0,
837        };
838        assert!(matches!(linear, GrowthPattern::Linear { .. }));
839    }
840}