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 > 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 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 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 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); 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 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); 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 assert_eq!(filtered.len(), 2);
901
902 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 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 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 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); 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 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); 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 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 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 assert_eq!(export_stats.user_variables, 2); assert_eq!(export_stats.system_allocations, 2); 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 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 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); 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); 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 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 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); assert_eq!(export_stats.system_allocations, 0); assert!(export_stats.output_size_bytes > 0);
1114
1115 Ok(())
1116 }
1117}