1use tracing::{debug, warn};
7
8use crate::analysis::memory_passport_tracker::MemoryPassportTracker;
9use crate::capture::platform::memory_info::PlatformMemoryInfo;
10use crate::core::{MemScopeError, MemScopeResult};
11use crate::render_engine::dashboard::{rebuild_allocations_from_events, DashboardRenderer};
12use crate::tracker::Tracker;
13use serde_json::json;
14use std::{
15 fs::File,
16 io::{BufWriter, Write},
17 path::Path,
18 sync::Arc,
19};
20
21mod ownership;
22mod snapshot_json;
23mod task_graph;
24pub use ownership::{export_ownership_graph_json, export_ownership_graph_snapshot};
25pub use snapshot_json::{
26 export_snapshot_to_json, export_snapshot_with_containers, ExportError, ExportJsonOptions,
27 OptimizationLevel, SchemaValidator,
28};
29pub use task_graph::{
30 export_task_graph_json, export_task_graph_json_with_accounting, export_task_graph_snapshot,
31};
32
33fn capture_cut_ns() -> u64 {
35 std::time::SystemTime::now()
36 .duration_since(std::time::UNIX_EPOCH)
37 .map(|elapsed| elapsed.as_nanos().min(u128::from(u64::MAX)) as u64)
38 .unwrap_or(0)
39}
40
41pub fn export_all_json<P: AsRef<Path>>(
42 path: P,
43 tracker: &Tracker,
44 passport_tracker: &Arc<MemoryPassportTracker>,
45 async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
46) -> MemScopeResult<()> {
47 let _suppression = crate::core::allocator::TrackingGuard::acquire();
57
58 let path_ref = path.as_ref();
59
60 let events = tracker.event_store().snapshot();
62 let passports = passport_tracker.snapshot_passports();
65 let mut task_graph = crate::task_registry::global_registry().export_graph();
66 let async_metadata = crate::capture::backends::profile_replay::ProfileMetadata::capture(
67 &task_graph,
68 Some(async_tracker),
69 );
70 async_metadata.reconcile_graph(&mut task_graph);
71 let captured_at = capture_cut_ns();
72 let async_rebuilt = crate::capture::backends::profile_replay::rebuild_at(
73 &events,
74 &task_graph,
75 &async_metadata,
76 captured_at,
77 );
78 let borrow_history =
79 crate::analysis::borrow_analysis::get_global_borrow_analyzer().get_borrow_history();
80
81 let mut ledger = crate::snapshot::BindingLedger::new();
86 for event in &events {
87 ledger.apply(event);
88 }
89 let snapshot = crate::snapshot::builder::project_snapshot(&ledger, captured_at);
90 let options = ExportJsonOptions::default();
91
92 std::fs::create_dir_all(path_ref)
93 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
94
95 debug!("Starting export_snapshot_to_json");
96
97 let containers: Vec<crate::capture::types::AllocationInfo> =
100 rebuild_allocations_from_events(&events)
101 .into_iter()
102 .filter(|allocation| allocation.ptr >= crate::analysis::VIRTUAL_PTR_BASE)
103 .collect();
104
105 export_snapshot_with_containers(&snapshot, &containers, path_ref, &options)
106 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
107
108 debug!("Completed export_snapshot_to_json");
109
110 debug!("Starting export_memory_passports_json");
111
112 write_memory_passports_json(path_ref, passport_tracker, &passports)
113 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
114
115 debug!("Completed export_memory_passports_json");
116
117 debug!("Starting export_leak_detection_json");
118
119 write_leak_detection_json(path_ref, &passport_tracker.detect_leaks_in(&passports))
120 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
121
122 debug!("Completed export_leak_detection_json");
123
124 debug!("Starting export_unsafe_ffi_json");
125
126 write_unsafe_ffi_json(path_ref, passport_tracker, &passports)
127 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
128
129 debug!("Completed export_unsafe_ffi_json");
130
131 debug!("Starting export_system_resources_json");
132
133 export_system_resources_json(path_ref)
134 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
135
136 debug!("Completed export_system_resources_json");
137
138 debug!("Starting export_async_analysis_json");
139
140 write_async_analysis_json(path_ref, async_rebuilt)
141 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
142
143 debug!("Completed export_async_analysis_json");
144
145 debug!("Starting export_ownership_graph_json");
146
147 let typed_allocations: Vec<crate::capture::types::AllocationInfo> =
151 rebuild_allocations_from_events(&events);
152
153 debug!(
154 allocations = typed_allocations.len(),
155 "Converted allocations to typed format"
156 );
157
158 export_ownership_graph_snapshot(
159 path_ref,
160 &typed_allocations,
161 &events,
162 &borrow_history,
163 captured_at,
164 )
165 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
166
167 debug!("Completed export_ownership_graph_json");
168
169 export_task_graph_snapshot(path_ref, &events, Some(&task_graph))
171 .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
172
173 debug!("Completed export_task_graph_json");
174
175 debug!("All exports completed successfully");
176
177 Ok(())
178}
179
180pub fn export_async_analysis_json<P: AsRef<Path>>(
187 path: P,
188 events: &[crate::event_store::MemoryEvent],
189 task_graph: &crate::task_registry::TaskGraph,
190 async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
191) -> MemScopeResult<()> {
192 let _suppression = crate::core::allocator::TrackingGuard::acquire();
198
199 let path_ref = path.as_ref();
200
201 let rebuilt =
204 crate::capture::backends::rebuild_with_live_tracker(events, task_graph, async_tracker);
205 write_async_analysis_json(path_ref, rebuilt)
206}
207
208fn write_async_analysis_json(
209 path_ref: &Path,
210 rebuilt: crate::capture::backends::profile_replay::RebuiltProfiles,
211) -> MemScopeResult<()> {
212 let profiles = rebuilt.profiles;
213 let live_allocations = rebuilt.live_allocations;
214
215 let total_tasks = profiles.len() as u64;
216 let active_tasks = profiles.iter().filter(|p| !p.is_completed()).count() as u64;
217
218 let async_data = json!({
219 "summary": {
220 "total_tasks": total_tasks,
221 "active_tasks": active_tasks,
222 "total_allocations": rebuilt.total_allocations,
223 "total_memory_bytes": rebuilt.total_memory,
224 "active_memory_bytes": rebuilt.current_memory,
225 "peak_memory_bytes": rebuilt.global_peak_memory,
226 },
227 "task_profiles": profiles.iter().map(|p| json!({
228 "task_id": p.task_id,
229 "task_name": p.task_name,
230 "task_type": format!("{:?}", p.task_type),
231 "created_at_ms": p.created_at_ms,
232 "completed_at_ms": p.completed_at_ms,
233 "total_bytes": p.total_bytes,
234 "current_memory": p.current_memory,
235 "peak_memory": p.peak_memory,
236 "total_allocations": p.total_allocations,
237 "total_deallocations": p.total_deallocations,
238 "duration_ns": p.duration_ns,
239 "allocation_rate": p.allocation_rate,
240 "efficiency_score": p.efficiency_score,
241 "average_allocation_size": p.average_allocation_size,
242 "is_completed": p.is_completed(),
243 "has_potential_leak": p.has_potential_leak(),
244 })).collect::<Vec<_>>(),
245 "allocations": live_allocations.iter().map(|a| json!({
246 "ptr": format!("0x{:x}", a.ptr),
247 "size": a.size,
248 "timestamp": a.timestamp,
249 "task_id": a.task_id,
250 "var_name": a.var_name,
251 "type_name": a.type_name,
252 })).collect::<Vec<_>>(),
253 });
254
255 let async_path = path_ref.join("async_analysis.json");
256 let file = File::create(async_path)
257 .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
258 let mut writer = BufWriter::new(file);
259 serde_json::to_writer_pretty(&mut writer, &async_data)
260 .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
261 writer
262 .flush()
263 .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
264
265 Ok(())
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Default)]
271pub enum DashboardTemplate {
272 #[default]
274 Unified,
275 Custom(String),
277}
278
279impl std::fmt::Display for DashboardTemplate {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 match self {
282 DashboardTemplate::Unified => write!(f, "dashboard_unified"),
283 DashboardTemplate::Custom(id) => write!(f, "{}", id),
284 }
285 }
286}
287
288impl DashboardTemplate {
289 pub fn template_id(&self) -> &str {
291 match self {
292 DashboardTemplate::Unified => "dashboard_unified",
293 DashboardTemplate::Custom(id) => id.as_str(),
294 }
295 }
296
297 pub fn name(&self) -> &str {
299 match self {
300 DashboardTemplate::Unified => "Unified Dashboard",
301 DashboardTemplate::Custom(_) => "Custom Template",
302 }
303 }
304}
305
306pub fn export_dashboard_html<P: AsRef<Path>>(
312 path: P,
313 tracker: &Tracker,
314 passport_tracker: &Arc<MemoryPassportTracker>,
315) -> MemScopeResult<()> {
316 export_dashboard_html_with_template(
317 path,
318 tracker,
319 passport_tracker,
320 DashboardTemplate::default(),
321 None,
322 )
323}
324
325pub fn export_dashboard_html_with_async<P: AsRef<Path>>(
327 path: P,
328 tracker: &Tracker,
329 passport_tracker: &Arc<MemoryPassportTracker>,
330 async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
331) -> MemScopeResult<()> {
332 export_dashboard_html_with_template(
333 path,
334 tracker,
335 passport_tracker,
336 DashboardTemplate::default(),
337 Some(async_tracker),
338 )
339}
340
341pub fn export_dashboard_html_with_template<P: AsRef<Path>>(
346 path: P,
347 tracker: &Tracker,
348 passport_tracker: &Arc<MemoryPassportTracker>,
349 template: DashboardTemplate,
350 async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
351) -> MemScopeResult<()> {
352 let _suppression = crate::core::allocator::TrackingGuard::acquire();
357
358 let path_ref = path.as_ref();
359
360 std::fs::create_dir_all(path_ref).map_err(|e| {
362 MemScopeError::error(
363 "export",
364 "export_dashboard_html_with_template",
365 format!("Failed to create output directory: {}", e),
366 )
367 })?;
368
369 let renderer = DashboardRenderer::new().map_err(|e| {
371 MemScopeError::error(
372 "export",
373 "export_dashboard_html_with_template",
374 format!("Failed to create dashboard renderer: {}", e),
375 )
376 })?;
377
378 let context = renderer
380 .build_context_from_tracker_with_async(tracker, passport_tracker, async_tracker)
381 .map_err(|e| {
382 MemScopeError::error(
383 "export",
384 "export_dashboard_html_with_template",
385 format!("Failed to build context: {}", e),
386 )
387 })?;
388
389 let html_content = match template {
390 DashboardTemplate::Unified | DashboardTemplate::Custom(_) => {
392 let template_id = template.template_id();
393 renderer
394 .render_with_template(template_id, &context)
395 .map_err(|e| {
396 MemScopeError::error(
397 "export",
398 "export_dashboard_html_with_template",
399 format!("Failed to render {} template: {}", template.name(), e),
400 )
401 })?
402 }
403 };
404
405 let output_file = path_ref.join(format!("{}_dashboard.html", template));
407 std::fs::write(&output_file, html_content).map_err(|e| {
408 MemScopeError::error(
409 "export",
410 "export_dashboard_html_with_template",
411 format!("Failed to write HTML file: {}", e),
412 )
413 })?;
414
415 tracing::info!("✅ Dashboard HTML exported to: {:?}", output_file);
416
417 Ok(())
418}
419
420pub fn export_memory_passports_json<P: AsRef<Path>>(
421 base_path: P,
422 passport_tracker: &Arc<MemoryPassportTracker>,
423) -> MemScopeResult<()> {
424 write_memory_passports_json(
425 base_path.as_ref(),
426 passport_tracker,
427 &passport_tracker.snapshot_passports(),
428 )
429}
430
431fn write_memory_passports_json(
432 base_path: &Path,
433 passport_tracker: &MemoryPassportTracker,
434 passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
435) -> MemScopeResult<()> {
436 let passport_data: Vec<_> = passports
437 .iter()
438 .map(|p| {
439 serde_json::json!({
440 "passport_id": p.passport_id,
441 "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
442 "generation": p.generation,
443 "superseded": p.superseded,
444 "size_bytes": p.size_bytes,
445 "created_at": p.created_at,
446 "lifecycle_events": p.lifecycle_events.len(),
447 "history_truncated": p.history_truncated,
448 "status": format!("{:?}", passport_tracker.assess_status(p)),
449 "status_source": "assessed_at_export",
450 })
451 })
452 .collect();
453
454 let json_data = serde_json::json!({
455 "metadata": {
456 "export_version": "2.0",
457 "specification": "memory passport tracking",
458 "total_passports": passports.len()
459 },
460 "memory_passports": passport_data,
461 });
462
463 let file_path = base_path.join("memory_passports.json");
464 let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
465 MemScopeError::error("export", "export_memory_passports_json", e.to_string())
466 })?;
467 std::fs::write(&file_path, json_string).map_err(|e| {
468 MemScopeError::error("export", "export_memory_passports_json", e.to_string())
469 })?;
470
471 Ok(())
472}
473
474pub fn export_leak_detection_json<P: AsRef<Path>>(
475 base_path: P,
476 passport_tracker: &Arc<MemoryPassportTracker>,
477) -> MemScopeResult<()> {
478 write_leak_detection_json(
479 base_path.as_ref(),
480 &passport_tracker.detect_leaks_at_shutdown(),
481 )
482}
483
484fn write_leak_detection_json(
485 base_path: &Path,
486 leak_result: &crate::analysis::memory_passport_tracker::LeakDetectionResult,
487) -> MemScopeResult<()> {
488 let leak_details: Vec<_> = leak_result
489 .leak_details
490 .iter()
491 .map(|detail| {
492 serde_json::json!({
493 "passport_id": detail.passport_id,
494 "memory_address": format!("0x{:x}", detail.memory_address),
495 "size_bytes": detail.size_bytes,
496 "lifecycle_summary": detail.lifecycle_summary,
497 })
498 })
499 .collect();
500
501 let json_data = serde_json::json!({
502 "metadata": {
503 "export_version": "2.0",
504 "specification": "leak detection",
505 "leaks_detected": leak_result.total_leaks
506 },
507 "leak_detection": {
508 "total_leaks": leak_result.total_leaks,
509 "leak_details": leak_details
510 }
511 });
512
513 let file_path = base_path.join("leak_detection.json");
514 let json_string = serde_json::to_string_pretty(&json_data)
515 .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
516 std::fs::write(&file_path, json_string)
517 .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
518
519 Ok(())
520}
521
522pub fn export_unsafe_ffi_json<P: AsRef<Path>>(
523 base_path: P,
524 passport_tracker: &Arc<MemoryPassportTracker>,
525) -> MemScopeResult<()> {
526 write_unsafe_ffi_json(
527 base_path.as_ref(),
528 passport_tracker,
529 &passport_tracker.snapshot_passports(),
530 )
531}
532
533fn write_unsafe_ffi_json(
534 base_path: &Path,
535 passport_tracker: &MemoryPassportTracker,
536 passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
537) -> MemScopeResult<()> {
538 use crate::analysis::memory_passport_tracker::PassportStatus;
539
540 let ffi_reports: Vec<_> = passports
541 .iter()
542 .filter(|p| {
543 matches!(
544 passport_tracker.assess_status(p),
545 PassportStatus::HandoverToFfi
546 | PassportStatus::InForeignCustody
547 | PassportStatus::FreedByForeign
548 )
549 })
550 .map(|p| {
551 serde_json::json!({
552 "passport_id": p.passport_id,
553 "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
554 "generation": p.generation,
555 "superseded": p.superseded,
556 "size_bytes": p.size_bytes,
557 "history_truncated": p.history_truncated,
558 "status": format!("{:?}", passport_tracker.assess_status(p)),
559 "created_at": p.created_at,
560 "boundary_events": p.lifecycle_events.iter().map(|e| {
561 serde_json::json!({
562 "timestamp": e.timestamp,
563 "event_type": format!("{:?}", e.event_type),
564 "context": e.context,
565 })
566 }).collect::<Vec<_>>(),
567 })
568 })
569 .collect();
570
571 let json_data = serde_json::json!({
572 "metadata": {
573 "export_version": "2.0",
574 "specification": "unsafe FFI tracking",
575 "total_ffi_reports": ffi_reports.len(),
576 "total_memory_passports": passports.len()
577 },
578 "unsafe_reports": ffi_reports,
579 "memory_passports": passports.len()
580 });
581
582 let file_path = base_path.join("unsafe_ffi.json");
583 let json_string = serde_json::to_string_pretty(&json_data)
584 .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
585 std::fs::write(&file_path, json_string)
586 .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
587
588 Ok(())
589}
590
591pub fn export_system_resources_json<P: AsRef<Path>>(base_path: P) -> MemScopeResult<()> {
598 let base_path = base_path.as_ref();
599
600 let mut memory_info = PlatformMemoryInfo::new();
602 let _ = memory_info.initialize();
603
604 let memory_stats = match memory_info.collect_stats() {
605 Ok(stats) => stats,
606 Err(e) => {
607 warn!(error = %e, "Failed to collect memory stats");
608 return Err(MemScopeError::error(
609 "export",
610 "export_system_resources_json",
611 e.to_string(),
612 ));
613 }
614 };
615
616 let system_info = match memory_info.get_system_info() {
618 Ok(info) => info,
619 Err(e) => {
620 warn!(error = %e, "Failed to collect system info");
621 return Err(MemScopeError::error(
622 "export",
623 "export_system_resources_json",
624 e.to_string(),
625 ));
626 }
627 };
628
629 let json_data = serde_json::json!({
631 "metadata": {
632 "export_version": "2.0",
633 "specification": "system resource monitoring",
634 "timestamp": std::time::SystemTime::now()
635 .duration_since(std::time::UNIX_EPOCH)
636 .unwrap_or_default()
637 .as_secs()
638 },
639 "system_info": {
640 "os_name": system_info.os_name,
641 "os_version": system_info.os_version,
642 "architecture": system_info.architecture,
643 "cpu_cores": system_info.cpu_cores,
644 "page_size": system_info.page_size,
645 "large_page_size": system_info.large_page_size,
646 "cpu_cache": {
647 "l1_cache_size": system_info.cpu_cache.l1_cache_size,
648 "l2_cache_size": system_info.cpu_cache.l2_cache_size,
649 "l3_cache_size": system_info.cpu_cache.l3_cache_size,
650 "cache_line_size": system_info.cpu_cache.cache_line_size
651 },
652 "mmu_info": {
653 "virtual_address_bits": system_info.mmu_info.virtual_address_bits,
654 "physical_address_bits": system_info.mmu_info.physical_address_bits,
655 "aslr_enabled": system_info.mmu_info.aslr_enabled,
656 "nx_bit_supported": system_info.mmu_info.nx_bit_supported
657 }
658 },
659 "memory_stats": {
660 "virtual_memory": {
661 "total_virtual": memory_stats.virtual_memory.total_virtual,
662 "available_virtual": memory_stats.virtual_memory.available_virtual,
663 "used_virtual": memory_stats.virtual_memory.used_virtual,
664 "reserved": memory_stats.virtual_memory.reserved,
665 "committed": memory_stats.virtual_memory.committed
666 },
667 "physical_memory": {
668 "total_physical": memory_stats.physical_memory.total_physical,
669 "available_physical": memory_stats.physical_memory.available_physical,
670 "used_physical": memory_stats.physical_memory.used_physical,
671 "cached": memory_stats.physical_memory.cached,
672 "buffers": memory_stats.physical_memory.buffers,
673 "swap": {
674 "total_swap": memory_stats.physical_memory.swap.total_swap,
675 "used_swap": memory_stats.physical_memory.swap.used_swap,
676 "available_swap": memory_stats.physical_memory.swap.available_swap,
677 "swap_in_rate": memory_stats.physical_memory.swap.swap_in_rate,
678 "swap_out_rate": memory_stats.physical_memory.swap.swap_out_rate
679 }
680 },
681 "process_memory": {
682 "virtual_size": memory_stats.process_memory.virtual_size,
683 "resident_size": memory_stats.process_memory.resident_size,
684 "shared_size": memory_stats.process_memory.shared_size,
685 "private_size": memory_stats.process_memory.private_size,
686 "heap_size": memory_stats.process_memory.heap_size,
687 "stack_size": memory_stats.process_memory.stack_size,
688 "mapped_files": memory_stats.process_memory.mapped_files,
689 "peak_usage": memory_stats.process_memory.peak_usage
690 },
691 "system_memory": {
692 "allocation_count": memory_stats.system_memory.allocation_count,
693 "deallocation_count": memory_stats.system_memory.deallocation_count,
694 "active_allocations": memory_stats.system_memory.active_allocations,
695 "total_allocated": memory_stats.system_memory.total_allocated,
696 "total_deallocated": memory_stats.system_memory.total_deallocated,
697 "fragmentation_level": memory_stats.system_memory.fragmentation_level,
698 "large_pages": {
699 "supported": memory_stats.system_memory.large_pages.supported,
700 "total_large_pages": memory_stats.system_memory.large_pages.total_large_pages,
701 "used_large_pages": memory_stats.system_memory.large_pages.used_large_pages,
702 "page_size": memory_stats.system_memory.large_pages.page_size
703 }
704 },
705 "pressure_indicators": {
706 "pressure_level": format!("{:?}", memory_stats.pressure_indicators.pressure_level),
707 "low_memory": memory_stats.pressure_indicators.low_memory,
708 "swapping_active": memory_stats.pressure_indicators.swapping_active,
709 "allocation_failure_rate": memory_stats.pressure_indicators.allocation_failure_rate,
710 "gc_pressure": memory_stats.pressure_indicators.gc_pressure
711 }
712 }
713 });
714
715 let file_path = base_path.join("system_resources.json");
716 let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
717 MemScopeError::error("export", "export_system_resources_json", e.to_string())
718 })?;
719 std::fs::write(&file_path, json_string).map_err(|e| {
720 MemScopeError::error("export", "export_system_resources_json", e.to_string())
721 })?;
722
723 Ok(())
724}
725
726#[cfg(test)]
727mod tests {
728 use super::snapshot_json::{estimate_json_size, get_or_compute_type_info};
729 use super::*;
730
731 #[test]
734 fn test_optimization_level_variants() {
735 let _low = OptimizationLevel::Low;
736 let _medium = OptimizationLevel::Medium;
737 let _high = OptimizationLevel::High;
738 let _maximum = OptimizationLevel::Maximum;
739 }
740
741 #[test]
744 fn test_optimization_level_default() {
745 let level = OptimizationLevel::default();
746 assert!(
747 matches!(level, OptimizationLevel::Medium),
748 "Default should be Medium"
749 );
750 }
751
752 #[test]
755 fn test_schema_validator_new() {
756 let validator = SchemaValidator::new();
757 let data = serde_json::json!({"test": "value"});
758 let result = validator.validate(&data);
759 assert!(result.is_ok(), "Validation should pass for any object");
760 }
761
762 #[test]
765 fn test_schema_validator_strict_mode() {
766 let validator = SchemaValidator::new().with_strict_mode(true);
767
768 let missing_fields = serde_json::json!({"other": "data"});
769 let result = validator.validate(&missing_fields);
770 assert!(result.is_err(), "Should fail with missing required fields");
771
772 let valid_data = serde_json::json!({
773 "timestamp": 123,
774 "allocations": [],
775 "stats": {}
776 });
777 let result = validator.validate(&valid_data);
778 assert!(result.is_ok(), "Should pass with all required fields");
779 }
780
781 #[test]
784 fn test_schema_validator_non_object() {
785 let validator = SchemaValidator::new();
786 let data = serde_json::json!("not an object");
787 let result = validator.validate(&data);
788 assert!(result.is_err(), "Should reject non-object data");
789 }
790
791 #[test]
794 fn test_export_json_options_default() {
795 let options = ExportJsonOptions::default();
796 assert!(
797 options.parallel_processing,
798 "parallel_processing should be true by default"
799 );
800 assert!(
801 options.streaming_writer,
802 "streaming_writer should be true by default"
803 );
804 assert!(
805 options.enable_type_cache,
806 "enable_type_cache should be true by default"
807 );
808 assert!(
809 options.adaptive_optimization,
810 "adaptive_optimization should be true by default"
811 );
812 assert!(
813 !options.schema_validation,
814 "schema_validation should be false by default"
815 );
816 assert!(
817 !options.security_analysis,
818 "security_analysis should be false by default"
819 );
820 }
821
822 #[test]
825 fn test_export_json_options_builders() {
826 let options = ExportJsonOptions::default()
827 .fast_export_mode(true)
828 .security_analysis(true)
829 .streaming_writer(false)
830 .schema_validation(true)
831 .integrity_hashes(true)
832 .batch_size(500)
833 .adaptive_optimization(false)
834 .max_cache_size(5000)
835 .include_low_severity(true)
836 .thread_count(Some(4));
837
838 assert!(options.fast_export_mode, "fast_export_mode should be true");
839 assert!(
840 options.security_analysis,
841 "security_analysis should be true"
842 );
843 assert!(
844 !options.streaming_writer,
845 "streaming_writer should be false"
846 );
847 assert!(
848 options.schema_validation,
849 "schema_validation should be true"
850 );
851 assert!(options.integrity_hashes, "integrity_hashes should be true");
852 assert_eq!(options.batch_size, 500, "batch_size should be 500");
853 assert!(
854 !options.adaptive_optimization,
855 "adaptive_optimization should be false"
856 );
857 assert_eq!(
858 options.max_cache_size, 5000,
859 "max_cache_size should be 5000"
860 );
861 assert!(
862 options.include_low_severity,
863 "include_low_severity should be true"
864 );
865 assert_eq!(
866 options.thread_count,
867 Some(4),
868 "thread_count should be Some(4)"
869 );
870 }
871
872 #[test]
875 fn test_export_error_variants() {
876 let io_err = ExportError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
877 let json_err = ExportError::Json(serde_json::from_str::<i32>("invalid").unwrap_err());
878 let export_err = ExportError::ExportFailed("test error".to_string());
879
880 assert!(
881 format!("{}", io_err).contains("IO error"),
882 "Should contain IO error"
883 );
884 assert!(
885 format!("{}", json_err).contains("JSON error"),
886 "Should contain JSON error"
887 );
888 assert!(
889 format!("{}", export_err).contains("Export failed"),
890 "Should contain Export failed"
891 );
892 }
893
894 #[test]
897 fn test_estimate_json_size() {
898 let string_val = serde_json::json!("hello world");
899 let num_val = serde_json::json!(42);
900 let array_val = serde_json::json!([1, 2, 3]);
901 let object_val = serde_json::json!({"key": "value"});
902
903 let string_size = estimate_json_size(&string_val);
904 let num_size = estimate_json_size(&num_val);
905 let array_size = estimate_json_size(&array_val);
906 let object_size = estimate_json_size(&object_val);
907
908 assert!(string_size > 0, "String size should be positive");
909 assert!(num_size > 0, "Number size should be positive");
910 assert!(array_size > 0, "Array size should be positive");
911 assert!(object_size > 0, "Object size should be positive");
912 }
913
914 #[test]
917 fn test_get_or_compute_type_info() {
918 assert_eq!(
919 get_or_compute_type_info("Vec<i32>", 100),
920 "dynamic_array",
921 "Vec should be dynamic_array"
922 );
923 assert_eq!(
924 get_or_compute_type_info("String", 24),
925 "string",
926 "String should be string"
927 );
928 assert_eq!(
929 get_or_compute_type_info("Box<i32>", 8),
930 "smart_pointer",
931 "Box should be smart_pointer"
932 );
933 assert_eq!(
934 get_or_compute_type_info("Rc<String>", 8),
935 "smart_pointer",
936 "Rc should be smart_pointer"
937 );
938 assert_eq!(
939 get_or_compute_type_info("Arc<i32>", 16),
940 "smart_pointer",
941 "Arc should be smart_pointer"
942 );
943 assert_eq!(
944 get_or_compute_type_info("[u8; 100]", 100),
945 "byte_array",
946 "u8 array should be byte_array"
947 );
948 assert_eq!(
949 get_or_compute_type_info("CustomType", 1024 * 1024 * 2),
950 "large_buffer",
951 "Large allocation should be large_buffer"
952 );
953 assert_eq!(
954 get_or_compute_type_info("MyType", 100),
955 "custom",
956 "Unknown type should be custom"
957 );
958 }
959}