memscope_rs/export/
api.rs

1//! Unified Export API - Clean, consistent interface for all export operations
2//!
3//! This module provides a unified, well-named API that serves as the main entry point
4//! for all export operations in the memscope project.
5
6use crate::core::tracker::export_json::ExportJsonOptions;
7use crate::core::tracker::memory_tracker::MemoryTracker;
8use crate::core::types::{AllocationInfo, MemoryStats, TrackingError};
9use crate::TrackingResult;
10use std::path::Path;
11use std::sync::Arc;
12use std::time::Instant;
13
14/// Export configuration with sensible defaults
15#[derive(Debug, Clone)]
16pub struct ExportConfig {
17    /// Include system allocations (default: false - user variables only)
18    pub include_system_allocations: bool,
19    /// Enable parallel processing for large datasets (default: auto-detect)
20    pub parallel_processing: Option<bool>,
21    /// Buffer size for I/O operations (default: 256KB)
22    pub buffer_size: usize,
23    /// Enable schema validation (default: true)
24    pub validate_output: bool,
25    /// Thread count for parallel operations (default: auto-detect)
26    pub thread_count: Option<usize>,
27}
28
29impl Default for ExportConfig {
30    fn default() -> Self {
31        Self {
32            include_system_allocations: false,
33            parallel_processing: None,
34            buffer_size: 256 * 1024, // 256KB
35            validate_output: true,
36            thread_count: None,
37        }
38    }
39}
40
41impl ExportConfig {
42    /// Create config for user variables only (recommended)
43    pub fn user_variables_only() -> Self {
44        Self {
45            include_system_allocations: false,
46            ..Default::default()
47        }
48    }
49
50    /// Create config for all allocations (system + user)
51    pub fn all_allocations() -> Self {
52        Self {
53            include_system_allocations: true,
54            ..Default::default()
55        }
56    }
57
58    /// Create config optimized for performance
59    pub fn fast_export() -> Self {
60        Self {
61            include_system_allocations: false,
62            parallel_processing: Some(true),
63            buffer_size: 512 * 1024, // 512KB
64            validate_output: false,
65            thread_count: None,
66        }
67    }
68
69    /// Create config for comprehensive analysis
70    pub fn comprehensive() -> Self {
71        Self {
72            include_system_allocations: true,
73            parallel_processing: Some(true),
74            buffer_size: 1024 * 1024, // 1MB
75            validate_output: true,
76            thread_count: None,
77        }
78    }
79}
80
81/// Export statistics and performance metrics
82#[derive(Debug, Clone, Default)]
83pub struct ExportStats {
84    /// Number of allocations processed
85    pub allocations_processed: usize,
86    /// Number of user-defined variables
87    pub user_variables: usize,
88    /// Number of system allocations
89    pub system_allocations: usize,
90    /// Total processing time in milliseconds
91    pub processing_time_ms: u64,
92    /// Output file size in bytes
93    pub output_size_bytes: u64,
94    /// Processing rate (allocations per second)
95    pub processing_rate: f64,
96}
97
98/// Unified export interface - main API for all export operations
99pub struct Exporter {
100    allocations: Arc<Vec<AllocationInfo>>,
101    #[allow(dead_code)]
102    stats: Arc<MemoryStats>,
103    config: ExportConfig,
104}
105
106impl Exporter {
107    /// Create new exporter with allocation data
108    pub fn new(allocations: Vec<AllocationInfo>, stats: MemoryStats, config: ExportConfig) -> Self {
109        Self {
110            allocations: Arc::new(allocations),
111            stats: Arc::new(stats),
112            config,
113        }
114    }
115
116    /// Filter allocations based on configuration
117    fn get_filtered_allocations(&self) -> Vec<AllocationInfo> {
118        if self.config.include_system_allocations {
119            // Include all allocations (user + system)
120            (*self.allocations).clone()
121        } else {
122            // Only include user-defined variables (allocations with var_name)
123            (*self.allocations)
124                .iter()
125                .filter(|allocation| allocation.var_name.is_some())
126                .cloned()
127                .collect()
128        }
129    }
130
131    /// Export to JSON format
132    pub fn export_json<P: AsRef<Path>>(&self, output_path: P) -> TrackingResult<ExportStats> {
133        let start_time = Instant::now();
134        let filtered_allocations = self.get_filtered_allocations();
135
136        // Ensure output directory exists
137        if let Some(parent) = output_path.as_ref().parent() {
138            std::fs::create_dir_all(parent).map_err(|e| TrackingError::IoError(e.to_string()))?;
139        }
140
141        // Use the actual allocation data instead of creating a new tracker
142        let tracker = MemoryTracker::new();
143
144        // Populate the tracker with our filtered allocations
145        for allocation in &filtered_allocations {
146            // Add each allocation to the tracker's active allocations
147            if let Ok(mut active) = tracker.active_allocations.try_lock() {
148                active.insert(allocation.ptr, allocation.clone());
149            }
150        }
151
152        tracker.export_to_json_with_options(
153            &output_path,
154            ExportJsonOptions::default()
155                .parallel_processing(self.config.parallel_processing.unwrap_or(true))
156                .buffer_size(self.config.buffer_size)
157                .schema_validation(self.config.validate_output),
158        )?;
159
160        let processing_time = start_time.elapsed();
161        let output_size = std::fs::metadata(&output_path)
162            .map(|m| m.len())
163            .unwrap_or(0);
164
165        Ok(ExportStats {
166            allocations_processed: self.allocations.len(),
167            user_variables: filtered_allocations.len(),
168            system_allocations: self.allocations.len() - filtered_allocations.len(),
169            processing_time_ms: processing_time.as_millis() as u64,
170            output_size_bytes: output_size,
171            processing_rate: self.allocations.len() as f64
172                / processing_time.as_secs_f64().max(0.001),
173        })
174    }
175
176    /// Export to binary format
177    pub fn export_binary<P: AsRef<Path>>(&self, output_path: P) -> TrackingResult<ExportStats> {
178        let start_time = Instant::now();
179        let filtered_allocations = self.get_filtered_allocations();
180
181        // Create a new tracker instance and populate it with our filtered allocations
182        let tracker = MemoryTracker::new();
183
184        // Populate the tracker with our filtered allocations (including improve.md fields)
185        if let Ok(mut active) = tracker.active_allocations.try_lock() {
186            for allocation in &filtered_allocations {
187                active.insert(allocation.ptr, allocation.clone());
188            }
189        }
190
191        tracker.export_to_binary(&output_path)?;
192
193        let processing_time = start_time.elapsed();
194        let output_size = std::fs::metadata(&output_path)
195            .map(|m| m.len())
196            .unwrap_or(0);
197
198        Ok(ExportStats {
199            allocations_processed: filtered_allocations.len(),
200            user_variables: filtered_allocations.len(),
201            system_allocations: 0,
202            processing_time_ms: processing_time.as_millis() as u64,
203            output_size_bytes: output_size,
204            processing_rate: filtered_allocations.len() as f64
205                / processing_time.as_secs_f64().max(0.001),
206        })
207    }
208
209    /// Export to HTML format
210    pub fn export_html<P: AsRef<Path>>(&self, output_path: P) -> TrackingResult<ExportStats> {
211        let start_time = Instant::now();
212        let filtered_allocations = self.get_filtered_allocations();
213
214        // Create a new tracker instance for export
215        let tracker = MemoryTracker::new();
216        tracker.export_interactive_dashboard(&output_path)?;
217
218        let processing_time = start_time.elapsed();
219        let output_size = std::fs::metadata(&output_path)
220            .map(|m| m.len())
221            .unwrap_or(0);
222
223        Ok(ExportStats {
224            allocations_processed: filtered_allocations.len(),
225            user_variables: filtered_allocations.len(),
226            system_allocations: 0,
227            processing_time_ms: processing_time.as_millis() as u64,
228            output_size_bytes: output_size,
229            processing_rate: filtered_allocations.len() as f64
230                / processing_time.as_secs_f64().max(0.001),
231        })
232    }
233
234    /// Convert binary to JSON format
235    pub fn binary_to_json<P: AsRef<Path>>(
236        binary_path: P,
237        output_path: P,
238    ) -> TrackingResult<ExportStats> {
239        let start_time = Instant::now();
240
241        // Get file size before conversion for stats
242        let input_size = std::fs::metadata(binary_path.as_ref())
243            .map(|m| m.len())
244            .unwrap_or(0);
245
246        // Delegate to binary module
247        crate::export::binary::parse_binary_to_json(binary_path.as_ref(), output_path.as_ref())
248            .map_err(|e| TrackingError::ExportError(e.to_string()))?;
249
250        let processing_time = start_time.elapsed();
251
252        // Get output file size
253        let output_size = std::fs::metadata(output_path.as_ref())
254            .map(|m| m.len())
255            .unwrap_or(0);
256
257        // Estimate allocations based on file size (approximate)
258        let estimated_allocations = input_size / 100; // Rough estimate
259
260        Ok(ExportStats {
261            allocations_processed: estimated_allocations as usize,
262            user_variables: estimated_allocations as usize, // Best guess
263            system_allocations: 0,                          // Can't determine from binary alone
264            processing_time_ms: processing_time.as_millis() as u64,
265            output_size_bytes: output_size,
266            processing_rate: estimated_allocations as f64
267                / processing_time.as_secs_f64().max(0.001),
268        })
269    }
270
271    /// Convert binary to HTML format
272    pub fn binary_to_html<P: AsRef<Path> + Clone>(
273        binary_path: P,
274        output_path: P,
275    ) -> TrackingResult<ExportStats> {
276        let start_time = Instant::now();
277
278        // Get file size before conversion for stats
279        let input_size = std::fs::metadata(&binary_path)
280            .map(|m| m.len())
281            .unwrap_or(0);
282
283        // Call the static method on MemoryTracker
284        MemoryTracker::export_binary_to_html(binary_path, output_path.clone())?;
285
286        let processing_time = start_time.elapsed();
287
288        // Get output file size
289        let output_size = std::fs::metadata(&output_path)
290            .map(|m| m.len())
291            .unwrap_or(0);
292
293        // Estimate allocations based on file size (approximate)
294        let estimated_allocations = input_size / 100; // Rough estimate
295
296        Ok(ExportStats {
297            allocations_processed: estimated_allocations as usize,
298            user_variables: estimated_allocations as usize, // Best guess
299            system_allocations: 0,                          // Can't determine from binary alone
300            processing_time_ms: processing_time.as_millis() as u64,
301            output_size_bytes: output_size,
302            processing_rate: estimated_allocations as f64
303                / processing_time.as_secs_f64().max(0.001),
304        })
305    }
306}
307
308// High-level convenience functions for common export scenarios
309// These are the main entry points for most users
310
311/// Export user variables to JSON format
312/// This is the most commonly used export function for development and debugging
313pub fn export_user_variables_json<P: AsRef<Path>>(
314    allocations: Vec<AllocationInfo>,
315    stats: MemoryStats,
316    output_path: P,
317) -> TrackingResult<ExportStats> {
318    let exporter = Exporter::new(allocations, stats, ExportConfig::user_variables_only());
319    exporter.export_json(output_path)
320}
321
322/// Export user variables to binary format
323/// Provides 3x faster export with 60% smaller file size compared to JSON
324pub fn export_user_variables_binary<P: AsRef<Path>>(
325    allocations: Vec<AllocationInfo>,
326    stats: MemoryStats,
327    output_path: P,
328) -> TrackingResult<ExportStats> {
329    let exporter = Exporter::new(allocations, stats, ExportConfig::user_variables_only());
330    exporter.export_binary(output_path)
331}
332
333/// Fast export for performance-critical scenarios
334/// Optimized for speed with reduced data quality checks
335pub fn export_fast<P: AsRef<Path>>(
336    allocations: Vec<AllocationInfo>,
337    stats: MemoryStats,
338    output_path: P,
339) -> TrackingResult<ExportStats> {
340    let exporter = Exporter::new(allocations, stats, ExportConfig::fast_export());
341    exporter.export_json(output_path)
342}
343
344/// Comprehensive export for detailed analysis
345/// Includes all system allocations and detailed analysis (slower but complete)
346pub fn export_comprehensive<P: AsRef<Path>>(
347    allocations: Vec<AllocationInfo>,
348    stats: MemoryStats,
349    output_path: P,
350) -> TrackingResult<ExportStats> {
351    let exporter = Exporter::new(allocations, stats, ExportConfig::comprehensive());
352    exporter.export_json(output_path)
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358    use tempfile::tempdir;
359
360    #[test]
361    fn test_export_config_defaults() {
362        let config = ExportConfig::default();
363        assert!(!config.include_system_allocations);
364        assert_eq!(config.buffer_size, 256 * 1024);
365        assert!(config.validate_output);
366    }
367
368    #[test]
369    fn test_export_json() -> TrackingResult<()> {
370        let temp_dir = tempdir()?;
371        let output_path = temp_dir.path().join("test.json");
372
373        let stats = export_user_variables_json(vec![], MemoryStats::default(), &output_path)?;
374
375        assert!(output_path.exists());
376        assert_eq!(stats.allocations_processed, 0);
377        Ok(())
378    }
379
380    fn create_test_allocation(
381        ptr: usize,
382        size: usize,
383        var_name: Option<String>,
384        type_name: Option<String>,
385    ) -> AllocationInfo {
386        AllocationInfo {
387            ptr,
388            size,
389            var_name,
390            type_name,
391            scope_name: Some("test_scope".to_string()),
392            timestamp_alloc: 1234567890,
393            timestamp_dealloc: None,
394            thread_id: "main".to_string(),
395            borrow_count: 0,
396            stack_trace: None,
397            is_leaked: false,
398            lifetime_ms: Some(100),
399            borrow_info: None,
400            clone_info: None,
401            ownership_history_available: false,
402            smart_pointer_info: None,
403            memory_layout: None,
404            generic_info: None,
405            dynamic_type_info: None,
406            runtime_state: None,
407            stack_allocation: None,
408            temporary_object: None,
409            fragmentation_analysis: None,
410            generic_instantiation: None,
411            type_relationships: None,
412            type_usage: None,
413            function_call_tracking: None,
414            lifecycle_tracking: None,
415            access_tracking: None,
416            drop_chain_analysis: None,
417        }
418    }
419
420    fn create_test_memory_stats() -> MemoryStats {
421        MemoryStats {
422            total_allocations: 10,
423            total_allocated: 1024,
424            active_allocations: 5,
425            active_memory: 512,
426            peak_allocations: 8,
427            peak_memory: 768,
428            total_deallocations: 5,
429            total_deallocated: 512,
430            leaked_allocations: 0,
431            leaked_memory: 0,
432            fragmentation_analysis: crate::core::types::FragmentationAnalysis::default(),
433            lifecycle_stats: crate::core::types::ScopeLifecycleMetrics::default(),
434            allocations: Vec::new(),
435            system_library_stats: crate::core::types::SystemLibraryStats::default(),
436            concurrency_analysis: crate::core::types::ConcurrencyAnalysis::default(),
437        }
438    }
439
440    #[test]
441    fn test_export_config_user_variables_only() {
442        let config = ExportConfig::user_variables_only();
443        assert!(!config.include_system_allocations);
444        assert_eq!(config.buffer_size, 256 * 1024);
445        assert!(config.validate_output);
446        assert!(config.parallel_processing.is_none());
447        assert!(config.thread_count.is_none());
448    }
449
450    #[test]
451    fn test_export_config_all_allocations() {
452        let config = ExportConfig::all_allocations();
453        assert!(config.include_system_allocations);
454        assert_eq!(config.buffer_size, 256 * 1024);
455        assert!(config.validate_output);
456        assert!(config.parallel_processing.is_none());
457        assert!(config.thread_count.is_none());
458    }
459
460    #[test]
461    fn test_export_config_fast_export() {
462        let config = ExportConfig::fast_export();
463        assert!(!config.include_system_allocations);
464        assert_eq!(config.buffer_size, 512 * 1024);
465        assert!(!config.validate_output);
466        assert_eq!(config.parallel_processing, Some(true));
467        assert!(config.thread_count.is_none());
468    }
469
470    #[test]
471    fn test_export_config_comprehensive() {
472        let config = ExportConfig::comprehensive();
473        assert!(config.include_system_allocations);
474        assert_eq!(config.buffer_size, 1024 * 1024);
475        assert!(config.validate_output);
476        assert_eq!(config.parallel_processing, Some(true));
477        assert!(config.thread_count.is_none());
478    }
479
480    #[test]
481    fn test_export_config_debug_clone() {
482        let config = ExportConfig::default();
483
484        // Test Debug trait
485        let debug_str = format!("{:?}", config);
486        assert!(debug_str.contains("ExportConfig"));
487        assert!(debug_str.contains("include_system_allocations"));
488        assert!(debug_str.contains("false")); // include_system_allocations is false by default
489
490        // Test Clone trait
491        let cloned_config = config.clone();
492        assert_eq!(
493            cloned_config.include_system_allocations,
494            config.include_system_allocations
495        );
496        assert_eq!(
497            cloned_config.parallel_processing,
498            config.parallel_processing
499        );
500        assert_eq!(cloned_config.buffer_size, config.buffer_size);
501        assert_eq!(cloned_config.validate_output, config.validate_output);
502        assert_eq!(cloned_config.thread_count, config.thread_count);
503    }
504
505    #[test]
506    fn test_export_stats_default() {
507        let stats = ExportStats::default();
508
509        assert_eq!(stats.allocations_processed, 0);
510        assert_eq!(stats.user_variables, 0);
511        assert_eq!(stats.system_allocations, 0);
512        assert_eq!(stats.processing_time_ms, 0);
513        assert_eq!(stats.output_size_bytes, 0);
514        assert_eq!(stats.processing_rate, 0.0);
515    }
516
517    #[test]
518    fn test_export_stats_debug_clone() {
519        let stats = ExportStats {
520            allocations_processed: 100,
521            user_variables: 80,
522            system_allocations: 20,
523            processing_time_ms: 1000,
524            output_size_bytes: 50000,
525            processing_rate: 100.0,
526        };
527
528        // Test Debug trait
529        let debug_str = format!("{:?}", stats);
530        assert!(debug_str.contains("ExportStats"));
531        assert!(debug_str.contains("100")); // allocations_processed
532        assert!(debug_str.contains("1000")); // processing_time_ms
533
534        // Test Clone trait
535        let cloned_stats = stats.clone();
536        assert_eq!(
537            cloned_stats.allocations_processed,
538            stats.allocations_processed
539        );
540        assert_eq!(cloned_stats.user_variables, stats.user_variables);
541        assert_eq!(cloned_stats.system_allocations, stats.system_allocations);
542        assert_eq!(cloned_stats.processing_time_ms, stats.processing_time_ms);
543        assert_eq!(cloned_stats.output_size_bytes, stats.output_size_bytes);
544        assert_eq!(cloned_stats.processing_rate, stats.processing_rate);
545    }
546
547    #[test]
548    fn test_exporter_new() {
549        let allocations = vec![
550            create_test_allocation(
551                0x1000,
552                64,
553                Some("var1".to_string()),
554                Some("String".to_string()),
555            ),
556            create_test_allocation(
557                0x2000,
558                128,
559                Some("var2".to_string()),
560                Some("Vec<i32>".to_string()),
561            ),
562        ];
563        let stats = create_test_memory_stats();
564        let config = ExportConfig::default();
565
566        let exporter = Exporter::new(allocations.clone(), stats, config);
567        assert_eq!(exporter.allocations.len(), 2);
568    }
569
570    #[test]
571    fn test_exporter_get_filtered_allocations() {
572        let allocations = vec![
573            // User allocations (have var_name)
574            create_test_allocation(
575                0x1000,
576                64,
577                Some("user_var1".to_string()),
578                Some("String".to_string()),
579            ),
580            create_test_allocation(
581                0x2000,
582                128,
583                Some("user_var2".to_string()),
584                Some("Vec<i32>".to_string()),
585            ),
586            // System allocations (no var_name)
587            create_test_allocation(0x3000, 32, None, Some("SystemType".to_string())),
588            create_test_allocation(0x4000, 16, None, None),
589        ];
590        let stats = create_test_memory_stats();
591
592        // Test with include_system_allocations = false (user_variables_only)
593        let config_user_only = ExportConfig::user_variables_only();
594        let exporter_user_only =
595            Exporter::new(allocations.clone(), stats.clone(), config_user_only);
596        let filtered_user_only = exporter_user_only.get_filtered_allocations();
597        assert_eq!(filtered_user_only.len(), 2); // Only user allocations with var_name
598
599        // Verify all filtered allocations have var_name
600        for allocation in &filtered_user_only {
601            assert!(
602                allocation.var_name.is_some(),
603                "User-only filter should only include allocations with var_name"
604            );
605        }
606
607        // Test with include_system_allocations = true (all_allocations)
608        let config_all = ExportConfig::all_allocations();
609        let exporter_all = Exporter::new(allocations.clone(), stats, config_all);
610        let filtered_all = exporter_all.get_filtered_allocations();
611        assert_eq!(filtered_all.len(), 4); // All allocations (user + system)
612    }
613
614    #[test]
615    fn test_exporter_export_json() -> TrackingResult<()> {
616        let temp_dir = tempdir()?;
617        let output_path = temp_dir.path().join("exporter_test.json");
618
619        let allocations = vec![create_test_allocation(
620            0x1000,
621            64,
622            Some("test_var".to_string()),
623            Some("String".to_string()),
624        )];
625        let stats = create_test_memory_stats();
626        let config = ExportConfig::default();
627
628        let exporter = Exporter::new(allocations, stats, config);
629        let export_stats = exporter.export_json(&output_path)?;
630
631        assert!(output_path.exists());
632        assert_eq!(export_stats.allocations_processed, 1);
633        assert_eq!(export_stats.user_variables, 1);
634        assert!(export_stats.processing_time_ms > 0);
635        assert!(export_stats.output_size_bytes > 0);
636        assert!(export_stats.processing_rate > 0.0);
637
638        Ok(())
639    }
640
641    #[test]
642    fn test_exporter_export_binary() -> TrackingResult<()> {
643        let temp_dir = tempdir()?;
644        let output_path = temp_dir.path().join("exporter_test.memscope");
645
646        let allocations = vec![create_test_allocation(
647            0x1000,
648            64,
649            Some("test_var".to_string()),
650            Some("String".to_string()),
651        )];
652        let stats = create_test_memory_stats();
653        let config = ExportConfig::default();
654
655        let exporter = Exporter::new(allocations, stats, config);
656        let export_stats = exporter.export_binary(&output_path)?;
657
658        assert!(output_path.exists());
659        assert_eq!(export_stats.allocations_processed, 1);
660        assert_eq!(export_stats.user_variables, 1);
661        assert_eq!(export_stats.system_allocations, 0);
662        assert!(export_stats.output_size_bytes > 0);
663        assert!(export_stats.processing_rate >= 0.0);
664
665        Ok(())
666    }
667
668    #[test]
669    fn test_export_user_variables_json() -> TrackingResult<()> {
670        let temp_dir = tempdir()?;
671        let output_path = temp_dir.path().join("user_vars.json");
672
673        let allocations = vec![
674            create_test_allocation(
675                0x1000,
676                64,
677                Some("user_var1".to_string()),
678                Some("String".to_string()),
679            ),
680            create_test_allocation(
681                0x2000,
682                128,
683                Some("user_var2".to_string()),
684                Some("Vec<i32>".to_string()),
685            ),
686        ];
687        let stats = create_test_memory_stats();
688
689        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
690
691        assert!(output_path.exists());
692        assert_eq!(export_stats.allocations_processed, 2);
693        assert_eq!(export_stats.user_variables, 2);
694        assert!(export_stats.output_size_bytes > 0);
695
696        Ok(())
697    }
698
699    #[test]
700    fn test_export_user_variables_binary() -> TrackingResult<()> {
701        let temp_dir = tempdir()?;
702        let output_path = temp_dir.path().join("user_vars.memscope");
703
704        let allocations = vec![
705            create_test_allocation(
706                0x1000,
707                64,
708                Some("user_var1".to_string()),
709                Some("String".to_string()),
710            ),
711            create_test_allocation(
712                0x2000,
713                128,
714                Some("user_var2".to_string()),
715                Some("Vec<i32>".to_string()),
716            ),
717        ];
718        let stats = create_test_memory_stats();
719
720        let export_stats = export_user_variables_binary(allocations, stats, &output_path)?;
721
722        assert!(output_path.exists());
723        assert_eq!(export_stats.allocations_processed, 2);
724        assert_eq!(export_stats.user_variables, 2);
725        assert!(export_stats.output_size_bytes > 0);
726
727        Ok(())
728    }
729
730    #[test]
731    fn test_export_fast() -> TrackingResult<()> {
732        let temp_dir = tempdir()?;
733        let output_path = temp_dir.path().join("fast_export.json");
734
735        let allocations = vec![create_test_allocation(
736            0x1000,
737            64,
738            Some("fast_var".to_string()),
739            Some("String".to_string()),
740        )];
741        let stats = create_test_memory_stats();
742
743        let export_stats = export_fast(allocations, stats, &output_path)?;
744
745        assert!(output_path.exists());
746        assert_eq!(export_stats.allocations_processed, 1);
747        assert_eq!(export_stats.user_variables, 1);
748        assert!(export_stats.output_size_bytes > 0);
749
750        Ok(())
751    }
752
753    #[test]
754    fn test_export_comprehensive() -> TrackingResult<()> {
755        let temp_dir = tempdir()?;
756        let output_path = temp_dir.path().join("comprehensive_export.json");
757
758        let allocations = vec![
759            create_test_allocation(
760                0x1000,
761                64,
762                Some("comp_var1".to_string()),
763                Some("String".to_string()),
764            ),
765            create_test_allocation(
766                0x2000,
767                128,
768                Some("comp_var2".to_string()),
769                Some("Vec<i32>".to_string()),
770            ),
771            create_test_allocation(
772                0x3000,
773                256,
774                Some("comp_var3".to_string()),
775                Some("HashMap<String, i32>".to_string()),
776            ),
777        ];
778        let stats = create_test_memory_stats();
779
780        let export_stats = export_comprehensive(allocations, stats, &output_path)?;
781
782        assert!(output_path.exists());
783        assert_eq!(export_stats.allocations_processed, 3);
784        assert_eq!(export_stats.user_variables, 3);
785        assert!(export_stats.output_size_bytes > 0);
786
787        Ok(())
788    }
789
790    #[test]
791    fn test_export_with_different_configs() -> TrackingResult<()> {
792        let temp_dir = tempdir()?;
793
794        let allocations = vec![create_test_allocation(
795            0x1000,
796            64,
797            Some("config_var".to_string()),
798            Some("String".to_string()),
799        )];
800        let stats = create_test_memory_stats();
801
802        // Test with fast config
803        let fast_config = ExportConfig::fast_export();
804        let fast_exporter = Exporter::new(allocations.clone(), stats.clone(), fast_config);
805        let fast_output = temp_dir.path().join("fast_config.json");
806        let _fast_stats = fast_exporter.export_json(&fast_output)?;
807        assert!(fast_output.exists());
808
809        // Test with comprehensive config
810        let comp_config = ExportConfig::comprehensive();
811        let comp_exporter = Exporter::new(allocations.clone(), stats.clone(), comp_config);
812        let comp_output = temp_dir.path().join("comp_config.json");
813        let _comp_stats = comp_exporter.export_json(&comp_output)?;
814        assert!(comp_output.exists());
815
816        // Test with custom config
817        let custom_config = ExportConfig {
818            include_system_allocations: true,
819            parallel_processing: Some(false),
820            buffer_size: 128 * 1024,
821            validate_output: false,
822            thread_count: Some(2),
823        };
824        let custom_exporter = Exporter::new(allocations, stats, custom_config);
825        let custom_output = temp_dir.path().join("custom_config.json");
826        let _custom_stats = custom_exporter.export_json(&custom_output)?;
827        assert!(custom_output.exists());
828
829        Ok(())
830    }
831
832    #[test]
833    fn test_export_empty_allocations() -> TrackingResult<()> {
834        let temp_dir = tempdir()?;
835        let output_path = temp_dir.path().join("empty.json");
836
837        let allocations = vec![];
838        let stats = create_test_memory_stats();
839
840        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
841
842        assert!(output_path.exists());
843        assert_eq!(export_stats.allocations_processed, 0);
844        assert_eq!(export_stats.user_variables, 0);
845        assert!(export_stats.output_size_bytes > 0); // JSON file should have some content
846
847        Ok(())
848    }
849
850    #[test]
851    fn test_export_large_dataset() -> TrackingResult<()> {
852        let temp_dir = tempdir()?;
853        let output_path = temp_dir.path().join("large_dataset.json");
854
855        // Create a large dataset
856        let mut allocations = Vec::new();
857        for i in 0..1000 {
858            allocations.push(create_test_allocation(
859                0x1000 + i * 0x100,
860                64 + i % 100,
861                Some(format!("var_{}", i)),
862                Some(format!("Type{}", i % 10)),
863            ));
864        }
865        let stats = create_test_memory_stats();
866
867        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
868
869        assert!(output_path.exists());
870        assert_eq!(export_stats.allocations_processed, 1000);
871        assert_eq!(export_stats.user_variables, 1000);
872        assert!(export_stats.output_size_bytes > 0); // Should have some content
873
874        Ok(())
875    }
876
877    #[test]
878    fn test_export_stats_calculations() {
879        let allocations = vec![
880            create_test_allocation(
881                0x1000,
882                64,
883                Some("var1".to_string()),
884                Some("String".to_string()),
885            ),
886            create_test_allocation(
887                0x2000,
888                128,
889                Some("var2".to_string()),
890                Some("Vec<i32>".to_string()),
891            ),
892        ];
893        let stats = create_test_memory_stats();
894        let config = ExportConfig::default();
895
896        let exporter = Exporter::new(allocations, stats, config);
897        let filtered = exporter.get_filtered_allocations();
898
899        // Verify filtering logic
900        assert_eq!(filtered.len(), 2);
901
902        // Test that all allocations have the expected structure
903        for allocation in &filtered {
904            assert!(allocation.ptr > 0);
905            assert!(allocation.size > 0);
906            assert!(allocation.var_name.is_some());
907            assert!(allocation.type_name.is_some());
908        }
909    }
910
911    #[test]
912    fn test_export_directory_creation() -> TrackingResult<()> {
913        let temp_dir = tempdir()?;
914        let nested_path = temp_dir
915            .path()
916            .join("nested")
917            .join("directory")
918            .join("test.json");
919
920        let allocations = vec![create_test_allocation(
921            0x1000,
922            64,
923            Some("dir_var".to_string()),
924            Some("String".to_string()),
925        )];
926        let stats = create_test_memory_stats();
927
928        let export_stats = export_user_variables_json(allocations, stats, &nested_path)?;
929
930        assert!(nested_path.exists());
931        assert!(nested_path.parent().unwrap().exists());
932        assert_eq!(export_stats.allocations_processed, 1);
933
934        Ok(())
935    }
936
937    #[test]
938    fn test_user_only_filtering_with_mixed_allocations() {
939        let allocations = vec![
940            // User allocations (have var_name)
941            create_test_allocation(
942                0x1000,
943                64,
944                Some("user_var1".to_string()),
945                Some("String".to_string()),
946            ),
947            create_test_allocation(
948                0x2000,
949                128,
950                Some("user_var2".to_string()),
951                Some("Vec<i32>".to_string()),
952            ),
953            create_test_allocation(
954                0x3000,
955                256,
956                Some("user_var3".to_string()),
957                Some("HashMap<String, i32>".to_string()),
958            ),
959            // System allocations (no var_name)
960            create_test_allocation(0x4000, 32, None, Some("SystemType1".to_string())),
961            create_test_allocation(0x5000, 16, None, Some("SystemType2".to_string())),
962            create_test_allocation(0x6000, 8, None, None),
963        ];
964        let stats = create_test_memory_stats();
965
966        // Test user_variables_only filtering
967        let config_user_only = ExportConfig::user_variables_only();
968        let exporter_user_only =
969            Exporter::new(allocations.clone(), stats.clone(), config_user_only);
970        let filtered_user_only = exporter_user_only.get_filtered_allocations();
971
972        assert_eq!(filtered_user_only.len(), 3); // Only user allocations
973        for allocation in &filtered_user_only {
974            assert!(
975                allocation.var_name.is_some(),
976                "User-only filter should only include allocations with var_name"
977            );
978            assert!(allocation
979                .var_name
980                .as_ref()
981                .unwrap()
982                .starts_with("user_var"));
983        }
984
985        // Test all_allocations filtering
986        let config_all = ExportConfig::all_allocations();
987        let exporter_all = Exporter::new(allocations.clone(), stats, config_all);
988        let filtered_all = exporter_all.get_filtered_allocations();
989
990        assert_eq!(filtered_all.len(), 6); // All allocations
991
992        let user_count = filtered_all.iter().filter(|a| a.var_name.is_some()).count();
993        let system_count = filtered_all.iter().filter(|a| a.var_name.is_none()).count();
994        assert_eq!(user_count, 3);
995        assert_eq!(system_count, 3);
996    }
997
998    #[test]
999    fn test_user_only_export_stats_accuracy() -> TrackingResult<()> {
1000        let temp_dir = tempdir()?;
1001        let output_path = temp_dir.path().join("user_only_stats.json");
1002
1003        let allocations = vec![
1004            // User allocations
1005            create_test_allocation(
1006                0x1000,
1007                64,
1008                Some("user_var1".to_string()),
1009                Some("String".to_string()),
1010            ),
1011            create_test_allocation(
1012                0x2000,
1013                128,
1014                Some("user_var2".to_string()),
1015                Some("Vec<i32>".to_string()),
1016            ),
1017            // System allocations
1018            create_test_allocation(0x3000, 32, None, Some("SystemType".to_string())),
1019            create_test_allocation(0x4000, 16, None, None),
1020        ];
1021        let stats = create_test_memory_stats();
1022
1023        let config = ExportConfig::user_variables_only();
1024        let exporter = Exporter::new(allocations, stats, config);
1025        let export_stats = exporter.export_json(&output_path)?;
1026
1027        // Verify export stats reflect correct filtering
1028        assert_eq!(export_stats.user_variables, 2); // Only user allocations exported
1029        assert_eq!(export_stats.system_allocations, 2); // System allocations not exported but counted
1030        assert!(export_stats.processing_time_ms > 0);
1031        assert!(export_stats.output_size_bytes > 0);
1032        assert!(export_stats.processing_rate > 0.0);
1033
1034        Ok(())
1035    }
1036
1037    #[test]
1038    fn test_user_only_edge_cases() {
1039        let stats = create_test_memory_stats();
1040
1041        // Test with empty allocations
1042        let empty_allocations: Vec<AllocationInfo> = vec![];
1043        let config = ExportConfig::user_variables_only();
1044        let exporter_empty = Exporter::new(empty_allocations, stats.clone(), config.clone());
1045        let filtered_empty = exporter_empty.get_filtered_allocations();
1046        assert_eq!(filtered_empty.len(), 0);
1047
1048        // Test with only system allocations
1049        let system_only = vec![
1050            create_test_allocation(0x1000, 32, None, Some("SystemType1".to_string())),
1051            create_test_allocation(0x2000, 16, None, None),
1052        ];
1053        let exporter_system = Exporter::new(system_only, stats.clone(), config.clone());
1054        let filtered_system = exporter_system.get_filtered_allocations();
1055        assert_eq!(filtered_system.len(), 0); // No user allocations
1056
1057        // Test with only user allocations
1058        let user_only = vec![
1059            create_test_allocation(
1060                0x1000,
1061                64,
1062                Some("user_var1".to_string()),
1063                Some("String".to_string()),
1064            ),
1065            create_test_allocation(
1066                0x2000,
1067                128,
1068                Some("user_var2".to_string()),
1069                Some("Vec<i32>".to_string()),
1070            ),
1071        ];
1072        let exporter_user = Exporter::new(user_only.clone(), stats, config);
1073        let filtered_user = exporter_user.get_filtered_allocations();
1074        assert_eq!(filtered_user.len(), 2); // All are user allocations
1075
1076        // Verify all filtered allocations have var_name
1077        for allocation in &filtered_user {
1078            assert!(allocation.var_name.is_some());
1079        }
1080    }
1081
1082    #[test]
1083    fn test_user_only_binary_export_integration() -> TrackingResult<()> {
1084        let temp_dir = tempdir()?;
1085        let binary_path = temp_dir.path().join("user_only_integration.memscope");
1086
1087        let allocations = vec![
1088            // User allocations
1089            create_test_allocation(
1090                0x1000,
1091                64,
1092                Some("user_var1".to_string()),
1093                Some("String".to_string()),
1094            ),
1095            create_test_allocation(
1096                0x2000,
1097                128,
1098                Some("user_var2".to_string()),
1099                Some("Vec<i32>".to_string()),
1100            ),
1101            // System allocations
1102            create_test_allocation(0x3000, 32, None, Some("SystemType".to_string())),
1103        ];
1104        let stats = create_test_memory_stats();
1105
1106        let config = ExportConfig::user_variables_only();
1107        let exporter = Exporter::new(allocations, stats, config);
1108        let export_stats = exporter.export_binary(&binary_path)?;
1109
1110        assert!(binary_path.exists());
1111        assert_eq!(export_stats.user_variables, 2); // Only user allocations
1112        assert_eq!(export_stats.system_allocations, 0); // No system allocations in binary export
1113        assert!(export_stats.output_size_bytes > 0);
1114
1115        Ok(())
1116    }
1117}