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        // processing_time_ms is u64, so >= 0 is always true - check it's reasonable instead
635        assert!(export_stats.processing_time_ms < 10000); // less than 10 seconds
636        assert!(export_stats.output_size_bytes > 0);
637        assert!(export_stats.processing_rate >= 0.0);
638
639        Ok(())
640    }
641
642    #[test]
643    fn test_exporter_export_binary() -> TrackingResult<()> {
644        let temp_dir = tempdir()?;
645        let output_path = temp_dir.path().join("exporter_test.memscope");
646
647        let allocations = vec![create_test_allocation(
648            0x1000,
649            64,
650            Some("test_var".to_string()),
651            Some("String".to_string()),
652        )];
653        let stats = create_test_memory_stats();
654        let config = ExportConfig::default();
655
656        let exporter = Exporter::new(allocations, stats, config);
657        let export_stats = exporter.export_binary(&output_path)?;
658
659        assert!(output_path.exists());
660        assert_eq!(export_stats.allocations_processed, 1);
661        assert_eq!(export_stats.user_variables, 1);
662        assert_eq!(export_stats.system_allocations, 0);
663        assert!(export_stats.output_size_bytes > 0);
664        assert!(export_stats.processing_rate >= 0.0);
665
666        Ok(())
667    }
668
669    #[test]
670    fn test_export_user_variables_json() -> TrackingResult<()> {
671        let temp_dir = tempdir()?;
672        let output_path = temp_dir.path().join("user_vars.json");
673
674        let allocations = vec![
675            create_test_allocation(
676                0x1000,
677                64,
678                Some("user_var1".to_string()),
679                Some("String".to_string()),
680            ),
681            create_test_allocation(
682                0x2000,
683                128,
684                Some("user_var2".to_string()),
685                Some("Vec<i32>".to_string()),
686            ),
687        ];
688        let stats = create_test_memory_stats();
689
690        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
691
692        assert!(output_path.exists());
693        assert_eq!(export_stats.allocations_processed, 2);
694        assert_eq!(export_stats.user_variables, 2);
695        assert!(export_stats.output_size_bytes > 0);
696
697        Ok(())
698    }
699
700    #[test]
701    fn test_export_user_variables_binary() -> TrackingResult<()> {
702        let temp_dir = tempdir()?;
703        let output_path = temp_dir.path().join("user_vars.memscope");
704
705        let allocations = vec![
706            create_test_allocation(
707                0x1000,
708                64,
709                Some("user_var1".to_string()),
710                Some("String".to_string()),
711            ),
712            create_test_allocation(
713                0x2000,
714                128,
715                Some("user_var2".to_string()),
716                Some("Vec<i32>".to_string()),
717            ),
718        ];
719        let stats = create_test_memory_stats();
720
721        let export_stats = export_user_variables_binary(allocations, stats, &output_path)?;
722
723        assert!(output_path.exists());
724        assert_eq!(export_stats.allocations_processed, 2);
725        assert_eq!(export_stats.user_variables, 2);
726        assert!(export_stats.output_size_bytes > 0);
727
728        Ok(())
729    }
730
731    #[test]
732    fn test_export_fast() -> TrackingResult<()> {
733        let temp_dir = tempdir()?;
734        let output_path = temp_dir.path().join("fast_export.json");
735
736        let allocations = vec![create_test_allocation(
737            0x1000,
738            64,
739            Some("fast_var".to_string()),
740            Some("String".to_string()),
741        )];
742        let stats = create_test_memory_stats();
743
744        let export_stats = export_fast(allocations, stats, &output_path)?;
745
746        assert!(output_path.exists());
747        assert_eq!(export_stats.allocations_processed, 1);
748        assert_eq!(export_stats.user_variables, 1);
749        assert!(export_stats.output_size_bytes > 0);
750
751        Ok(())
752    }
753
754    #[test]
755    fn test_export_comprehensive() -> TrackingResult<()> {
756        let temp_dir = tempdir()?;
757        let output_path = temp_dir.path().join("comprehensive_export.json");
758
759        let allocations = vec![
760            create_test_allocation(
761                0x1000,
762                64,
763                Some("comp_var1".to_string()),
764                Some("String".to_string()),
765            ),
766            create_test_allocation(
767                0x2000,
768                128,
769                Some("comp_var2".to_string()),
770                Some("Vec<i32>".to_string()),
771            ),
772            create_test_allocation(
773                0x3000,
774                256,
775                Some("comp_var3".to_string()),
776                Some("HashMap<String, i32>".to_string()),
777            ),
778        ];
779        let stats = create_test_memory_stats();
780
781        let export_stats = export_comprehensive(allocations, stats, &output_path)?;
782
783        assert!(output_path.exists());
784        assert_eq!(export_stats.allocations_processed, 3);
785        assert_eq!(export_stats.user_variables, 3);
786        assert!(export_stats.output_size_bytes > 0);
787
788        Ok(())
789    }
790
791    #[test]
792    fn test_export_with_different_configs() -> TrackingResult<()> {
793        let temp_dir = tempdir()?;
794
795        let allocations = vec![create_test_allocation(
796            0x1000,
797            64,
798            Some("config_var".to_string()),
799            Some("String".to_string()),
800        )];
801        let stats = create_test_memory_stats();
802
803        // Test with fast config
804        let fast_config = ExportConfig::fast_export();
805        let fast_exporter = Exporter::new(allocations.clone(), stats.clone(), fast_config);
806        let fast_output = temp_dir.path().join("fast_config.json");
807        let _fast_stats = fast_exporter.export_json(&fast_output)?;
808        assert!(fast_output.exists());
809
810        // Test with comprehensive config
811        let comp_config = ExportConfig::comprehensive();
812        let comp_exporter = Exporter::new(allocations.clone(), stats.clone(), comp_config);
813        let comp_output = temp_dir.path().join("comp_config.json");
814        let _comp_stats = comp_exporter.export_json(&comp_output)?;
815        assert!(comp_output.exists());
816
817        // Test with custom config
818        let custom_config = ExportConfig {
819            include_system_allocations: true,
820            parallel_processing: Some(false),
821            buffer_size: 128 * 1024,
822            validate_output: false,
823            thread_count: Some(2),
824        };
825        let custom_exporter = Exporter::new(allocations, stats, custom_config);
826        let custom_output = temp_dir.path().join("custom_config.json");
827        let _custom_stats = custom_exporter.export_json(&custom_output)?;
828        assert!(custom_output.exists());
829
830        Ok(())
831    }
832
833    #[test]
834    fn test_export_empty_allocations() -> TrackingResult<()> {
835        let temp_dir = tempdir()?;
836        let output_path = temp_dir.path().join("empty.json");
837
838        let allocations = vec![];
839        let stats = create_test_memory_stats();
840
841        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
842
843        assert!(output_path.exists());
844        assert_eq!(export_stats.allocations_processed, 0);
845        assert_eq!(export_stats.user_variables, 0);
846        assert!(export_stats.output_size_bytes > 0); // JSON file should have some content
847
848        Ok(())
849    }
850
851    #[test]
852    fn test_export_large_dataset() -> TrackingResult<()> {
853        let temp_dir = tempdir()?;
854        let output_path = temp_dir.path().join("large_dataset.json");
855
856        // Create a large dataset
857        let mut allocations = Vec::new();
858        for i in 0..1000 {
859            allocations.push(create_test_allocation(
860                0x1000 + i * 0x100,
861                64 + i % 100,
862                Some(format!("var_{}", i)),
863                Some(format!("Type{}", i % 10)),
864            ));
865        }
866        let stats = create_test_memory_stats();
867
868        let export_stats = export_user_variables_json(allocations, stats, &output_path)?;
869
870        assert!(output_path.exists());
871        assert_eq!(export_stats.allocations_processed, 1000);
872        assert_eq!(export_stats.user_variables, 1000);
873        assert!(export_stats.output_size_bytes > 0); // Should have some content
874
875        Ok(())
876    }
877
878    #[test]
879    fn test_export_stats_calculations() {
880        let allocations = vec![
881            create_test_allocation(
882                0x1000,
883                64,
884                Some("var1".to_string()),
885                Some("String".to_string()),
886            ),
887            create_test_allocation(
888                0x2000,
889                128,
890                Some("var2".to_string()),
891                Some("Vec<i32>".to_string()),
892            ),
893        ];
894        let stats = create_test_memory_stats();
895        let config = ExportConfig::default();
896
897        let exporter = Exporter::new(allocations, stats, config);
898        let filtered = exporter.get_filtered_allocations();
899
900        // Verify filtering logic
901        assert_eq!(filtered.len(), 2);
902
903        // Test that all allocations have the expected structure
904        for allocation in &filtered {
905            assert!(allocation.ptr > 0);
906            assert!(allocation.size > 0);
907            assert!(allocation.var_name.is_some());
908            assert!(allocation.type_name.is_some());
909        }
910    }
911
912    #[test]
913    fn test_export_directory_creation() -> TrackingResult<()> {
914        let temp_dir = tempdir()?;
915        let nested_path = temp_dir
916            .path()
917            .join("nested")
918            .join("directory")
919            .join("test.json");
920
921        let allocations = vec![create_test_allocation(
922            0x1000,
923            64,
924            Some("dir_var".to_string()),
925            Some("String".to_string()),
926        )];
927        let stats = create_test_memory_stats();
928
929        let export_stats = export_user_variables_json(allocations, stats, &nested_path)?;
930
931        assert!(nested_path.exists());
932        assert!(nested_path.parent().unwrap().exists());
933        assert_eq!(export_stats.allocations_processed, 1);
934
935        Ok(())
936    }
937
938    #[test]
939    fn test_user_only_filtering_with_mixed_allocations() {
940        let allocations = vec![
941            // User allocations (have var_name)
942            create_test_allocation(
943                0x1000,
944                64,
945                Some("user_var1".to_string()),
946                Some("String".to_string()),
947            ),
948            create_test_allocation(
949                0x2000,
950                128,
951                Some("user_var2".to_string()),
952                Some("Vec<i32>".to_string()),
953            ),
954            create_test_allocation(
955                0x3000,
956                256,
957                Some("user_var3".to_string()),
958                Some("HashMap<String, i32>".to_string()),
959            ),
960            // System allocations (no var_name)
961            create_test_allocation(0x4000, 32, None, Some("SystemType1".to_string())),
962            create_test_allocation(0x5000, 16, None, Some("SystemType2".to_string())),
963            create_test_allocation(0x6000, 8, None, None),
964        ];
965        let stats = create_test_memory_stats();
966
967        // Test user_variables_only filtering
968        let config_user_only = ExportConfig::user_variables_only();
969        let exporter_user_only =
970            Exporter::new(allocations.clone(), stats.clone(), config_user_only);
971        let filtered_user_only = exporter_user_only.get_filtered_allocations();
972
973        assert_eq!(filtered_user_only.len(), 3); // Only user allocations
974        for allocation in &filtered_user_only {
975            assert!(
976                allocation.var_name.is_some(),
977                "User-only filter should only include allocations with var_name"
978            );
979            assert!(allocation
980                .var_name
981                .as_ref()
982                .unwrap()
983                .starts_with("user_var"));
984        }
985
986        // Test all_allocations filtering
987        let config_all = ExportConfig::all_allocations();
988        let exporter_all = Exporter::new(allocations.clone(), stats, config_all);
989        let filtered_all = exporter_all.get_filtered_allocations();
990
991        assert_eq!(filtered_all.len(), 6); // All allocations
992
993        let user_count = filtered_all.iter().filter(|a| a.var_name.is_some()).count();
994        let system_count = filtered_all.iter().filter(|a| a.var_name.is_none()).count();
995        assert_eq!(user_count, 3);
996        assert_eq!(system_count, 3);
997    }
998
999    #[test]
1000    fn test_user_only_export_stats_accuracy() -> TrackingResult<()> {
1001        let temp_dir = tempdir()?;
1002        let output_path = temp_dir.path().join("user_only_stats.json");
1003
1004        let allocations = vec![
1005            // User allocations
1006            create_test_allocation(
1007                0x1000,
1008                64,
1009                Some("user_var1".to_string()),
1010                Some("String".to_string()),
1011            ),
1012            create_test_allocation(
1013                0x2000,
1014                128,
1015                Some("user_var2".to_string()),
1016                Some("Vec<i32>".to_string()),
1017            ),
1018            // System allocations
1019            create_test_allocation(0x3000, 32, None, Some("SystemType".to_string())),
1020            create_test_allocation(0x4000, 16, None, None),
1021        ];
1022        let stats = create_test_memory_stats();
1023
1024        let config = ExportConfig::user_variables_only();
1025        let exporter = Exporter::new(allocations, stats, config);
1026        let export_stats = exporter.export_json(&output_path)?;
1027
1028        // Verify export stats reflect correct filtering
1029        assert_eq!(export_stats.user_variables, 2); // Only user allocations exported
1030        assert_eq!(export_stats.system_allocations, 2); // System allocations not exported but counted
1031                                                        // processing_time_ms is u64, so >= 0 is always true - check it's reasonable instead
1032        assert!(export_stats.processing_time_ms < 10000); // less than 10 seconds
1033        assert!(export_stats.output_size_bytes > 0);
1034        assert!(export_stats.processing_rate >= 0.0);
1035
1036        Ok(())
1037    }
1038
1039    #[test]
1040    fn test_user_only_edge_cases() {
1041        let stats = create_test_memory_stats();
1042
1043        // Test with empty allocations
1044        let empty_allocations: Vec<AllocationInfo> = vec![];
1045        let config = ExportConfig::user_variables_only();
1046        let exporter_empty = Exporter::new(empty_allocations, stats.clone(), config.clone());
1047        let filtered_empty = exporter_empty.get_filtered_allocations();
1048        assert_eq!(filtered_empty.len(), 0);
1049
1050        // Test with only system allocations
1051        let system_only = vec![
1052            create_test_allocation(0x1000, 32, None, Some("SystemType1".to_string())),
1053            create_test_allocation(0x2000, 16, None, None),
1054        ];
1055        let exporter_system = Exporter::new(system_only, stats.clone(), config.clone());
1056        let filtered_system = exporter_system.get_filtered_allocations();
1057        assert_eq!(filtered_system.len(), 0); // No user allocations
1058
1059        // Test with only user allocations
1060        let user_only = vec![
1061            create_test_allocation(
1062                0x1000,
1063                64,
1064                Some("user_var1".to_string()),
1065                Some("String".to_string()),
1066            ),
1067            create_test_allocation(
1068                0x2000,
1069                128,
1070                Some("user_var2".to_string()),
1071                Some("Vec<i32>".to_string()),
1072            ),
1073        ];
1074        let exporter_user = Exporter::new(user_only.clone(), stats, config);
1075        let filtered_user = exporter_user.get_filtered_allocations();
1076        assert_eq!(filtered_user.len(), 2); // All are user allocations
1077
1078        // Verify all filtered allocations have var_name
1079        for allocation in &filtered_user {
1080            assert!(allocation.var_name.is_some());
1081        }
1082    }
1083
1084    #[test]
1085    fn test_user_only_binary_export_integration() -> TrackingResult<()> {
1086        let temp_dir = tempdir()?;
1087        let binary_path = temp_dir.path().join("user_only_integration.memscope");
1088
1089        let allocations = vec![
1090            // User allocations
1091            create_test_allocation(
1092                0x1000,
1093                64,
1094                Some("user_var1".to_string()),
1095                Some("String".to_string()),
1096            ),
1097            create_test_allocation(
1098                0x2000,
1099                128,
1100                Some("user_var2".to_string()),
1101                Some("Vec<i32>".to_string()),
1102            ),
1103            // System allocations
1104            create_test_allocation(0x3000, 32, None, Some("SystemType".to_string())),
1105        ];
1106        let stats = create_test_memory_stats();
1107
1108        let config = ExportConfig::user_variables_only();
1109        let exporter = Exporter::new(allocations, stats, config);
1110        let export_stats = exporter.export_binary(&binary_path)?;
1111
1112        assert!(binary_path.exists());
1113        assert_eq!(export_stats.user_variables, 2); // Only user allocations
1114        assert_eq!(export_stats.system_allocations, 0); // No system allocations in binary export
1115        assert!(export_stats.output_size_bytes > 0);
1116
1117        Ok(())
1118    }
1119}