1use 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#[derive(Debug, Clone)]
16pub struct ExportConfig {
17 pub include_system_allocations: bool,
19 pub parallel_processing: Option<bool>,
21 pub buffer_size: usize,
23 pub validate_output: bool,
25 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, validate_output: true,
36 thread_count: None,
37 }
38 }
39}
40
41impl ExportConfig {
42 pub fn user_variables_only() -> Self {
44 Self {
45 include_system_allocations: false,
46 ..Default::default()
47 }
48 }
49
50 pub fn all_allocations() -> Self {
52 Self {
53 include_system_allocations: true,
54 ..Default::default()
55 }
56 }
57
58 pub fn fast_export() -> Self {
60 Self {
61 include_system_allocations: false,
62 parallel_processing: Some(true),
63 buffer_size: 512 * 1024, validate_output: false,
65 thread_count: None,
66 }
67 }
68
69 pub fn comprehensive() -> Self {
71 Self {
72 include_system_allocations: true,
73 parallel_processing: Some(true),
74 buffer_size: 1024 * 1024, validate_output: true,
76 thread_count: None,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Default)]
83pub struct ExportStats {
84 pub allocations_processed: usize,
86 pub user_variables: usize,
88 pub system_allocations: usize,
90 pub processing_time_ms: u64,
92 pub output_size_bytes: u64,
94 pub processing_rate: f64,
96}
97
98pub struct Exporter {
100 allocations: Arc<Vec<AllocationInfo>>,
101 #[allow(dead_code)]
102 stats: Arc<MemoryStats>,
103 config: ExportConfig,
104}
105
106impl Exporter {
107 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 fn get_filtered_allocations(&self) -> Vec<AllocationInfo> {
118 if self.config.include_system_allocations {
119 (*self.allocations).clone()
121 } else {
122 (*self.allocations)
124 .iter()
125 .filter(|allocation| allocation.var_name.is_some())
126 .cloned()
127 .collect()
128 }
129 }
130
131 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 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 let tracker = MemoryTracker::new();
143
144 for allocation in &filtered_allocations {
146 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 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 let tracker = MemoryTracker::new();
183
184 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 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 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 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 let input_size = std::fs::metadata(binary_path.as_ref())
243 .map(|m| m.len())
244 .unwrap_or(0);
245
246 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 let output_size = std::fs::metadata(output_path.as_ref())
254 .map(|m| m.len())
255 .unwrap_or(0);
256
257 let estimated_allocations = input_size / 100; Ok(ExportStats {
261 allocations_processed: estimated_allocations as usize,
262 user_variables: estimated_allocations as usize, system_allocations: 0, 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 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 let input_size = std::fs::metadata(&binary_path)
280 .map(|m| m.len())
281 .unwrap_or(0);
282
283 MemoryTracker::export_binary_to_html(binary_path, output_path.clone())?;
285
286 let processing_time = start_time.elapsed();
287
288 let output_size = std::fs::metadata(&output_path)
290 .map(|m| m.len())
291 .unwrap_or(0);
292
293 let estimated_allocations = input_size / 100; Ok(ExportStats {
297 allocations_processed: estimated_allocations as usize,
298 user_variables: estimated_allocations as usize, system_allocations: 0, 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
308pub 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
322pub 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
333pub 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
344pub 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 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")); 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 let debug_str = format!("{:?}", stats);
530 assert!(debug_str.contains("ExportStats"));
531 assert!(debug_str.contains("100")); assert!(debug_str.contains("1000")); 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 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 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 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); 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 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); }
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 < 10000); 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 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 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 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); 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 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); 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 assert_eq!(filtered.len(), 2);
902
903 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 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 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 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); 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 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); 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 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 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 assert_eq!(export_stats.user_variables, 2); assert_eq!(export_stats.system_allocations, 2); assert!(export_stats.processing_time_ms < 10000); 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 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 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); 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); 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 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 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); assert_eq!(export_stats.system_allocations, 0); assert!(export_stats.output_size_bytes > 0);
1116
1117 Ok(())
1118 }
1119}