1#![allow(dead_code)]
6use clap::ArgMatches;
7use std::error::Error;
8use std::path::Path;
9use std::process::{Command, Stdio};
10
11pub fn run_analyze(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
13 let command_args: Vec<&String> = matches
15 .get_many::<String>("command")
16 .ok_or("Command argument is required")?
17 .collect();
18 let export_format = matches
19 .get_one::<String>("export")
20 .map(|s| s.as_str())
21 .unwrap_or("html");
22 let output_path = matches
23 .get_one::<String>("output")
24 .map(|s| s.as_str())
25 .unwrap_or("memory_analysis");
26
27 tracing::info!("🔍 Starting memory analysis...");
28 tracing::info!("Command: {:?}", command_args);
29 tracing::info!("Export format: {}", export_format);
30 tracing::info!("Output path: {}", output_path);
31
32 crate::init();
34
35 execute_with_tracking(&command_args, &[])?;
37
38 Ok(())
39}
40
41fn _original_main() {
42 use clap::{Arg, Command as ClapCommand};
43 let matches = ClapCommand::new("memscope")
44 .version("0.1.0")
45 .author("MemScope Team")
46 .about("Memory analysis tool for Rust programs")
47 .subcommand(
48 ClapCommand::new("run")
49 .about("Run and track program memory")
50 .arg(
51 Arg::new("command")
52 .help("Command to run (e.g., 'cargo run --release')")
53 .required(true)
54 .num_args(1..),
55 )
56 .arg(
57 Arg::new("export")
58 .long("export")
59 .value_name("FORMAT")
60 .help("Export format: json, html, or both")
61 .value_parser(["json", "html", "both"])
62 .default_value("json"),
63 )
64 .arg(
65 Arg::new("output")
66 .short('o')
67 .long("output")
68 .value_name("PATH")
69 .help("Output file path (without extension)")
70 .default_value("memscope_analysis"),
71 )
72 .arg(
73 Arg::new("auto-track")
74 .long("auto-track")
75 .help("Automatically track all allocations")
76 .action(clap::ArgAction::SetTrue),
77 )
78 .arg(
79 Arg::new("wait-completion")
80 .long("wait-completion")
81 .help("Wait for program completion before exporting")
82 .action(clap::ArgAction::SetTrue),
83 ),
84 )
85 .subcommand(
86 ClapCommand::new("analyze")
87 .about("Analyze existing memory snapshot")
88 .arg(Arg::new("input").help("Input JSON file").required(true))
89 .arg(Arg::new("output").help("Output HTML file").required(true))
90 .arg(
91 Arg::new("format")
92 .long("format")
93 .value_name("FORMAT")
94 .help("Output format: html, svg, or both")
95 .value_parser(["html", "svg", "both"])
96 .default_value("html"),
97 ),
98 )
99 .arg(
101 Arg::new("export")
102 .long("export")
103 .value_name("FORMAT")
104 .help("Export format: json, html, or both (legacy mode)")
105 .value_parser(["json", "html", "both"]),
106 )
107 .arg(
108 Arg::new("output")
109 .short('o')
110 .long("output")
111 .value_name("PATH")
112 .help("Output file path (legacy mode)")
113 .default_value("memscope_analysis"),
114 )
115 .arg(
116 Arg::new("auto-track")
117 .long("auto-track")
118 .help("Automatically track all allocations (legacy mode)")
119 .action(clap::ArgAction::SetTrue),
120 )
121 .arg(
122 Arg::new("command")
123 .help("Command to run (legacy mode)")
124 .num_args(1..),
125 )
126 .get_matches();
127
128 tracing::info!("🚀 MemScope - Memory Analysis Tool");
129
130 match matches.subcommand() {
131 Some(("run", _sub_matches)) => {
132 tracing::info!("Run command is deprecated. Use 'analyze' instead.");
134 }
135 Some(("analyze", sub_matches)) => {
136 if let Err(e) = handle_analyze_command(sub_matches) {
137 eprintln!("Error in analyze command: {e}");
138 std::process::exit(1);
139 }
140 }
141 _ => {
142 if let Err(e) = handle_legacy_mode(&matches) {
144 eprintln!("Error in legacy mode: {e}");
145 std::process::exit(1);
146 }
147 }
148 }
149}
150
151fn handle_run_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
152 let command_args: Vec<&String> = matches
153 .get_many::<String>("command")
154 .ok_or("Missing command arguments")?
155 .collect();
156 let export_format = matches
157 .get_one::<String>("export")
158 .ok_or("Missing export format")?;
159 let output_path = matches
160 .get_one::<String>("output")
161 .ok_or("Missing output path")?;
162 let auto_track = matches.get_flag("auto-track");
163 let wait_completion = matches.get_flag("wait-completion");
164
165 tracing::info!(
166 "📋 Command: {}",
167 command_args
168 .iter()
169 .map(|s| s.as_str())
170 .collect::<Vec<_>>()
171 .join(" ")
172 );
173 tracing::info!("📊 Export format: {export_format}");
174 tracing::info!("📁 Output path: {output_path}");
175
176 if auto_track {
177 tracing::info!("🔍 Auto-tracking enabled");
178 }
179
180 if wait_completion {
181 tracing::info!("⏳ Wait-for-completion enabled");
182 }
183
184 let mut env_vars = vec![
186 ("MEMSCOPE_ENABLED", "1"),
187 ("MEMSCOPE_AUTO_EXPORT", "1"),
188 ("MEMSCOPE_EXPORT_FORMAT", export_format),
189 ("MEMSCOPE_EXPORT_PATH", output_path),
190 ];
191
192 if auto_track {
193 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
194 }
195
196 if wait_completion {
197 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
198 }
199
200 let result = execute_with_tracking(&command_args, &env_vars);
202
203 match result {
204 Ok(()) => {
205 tracing::info!("✅ Program execution completed successfully");
206 tracing::info!("📊 Memory analysis exported to: {output_path}");
207
208 Ok(())
211 }
212 Err(e) => {
213 tracing::error!("❌ Program execution failed: {e}");
214 Err(e)
215 }
216 }
217}
218
219fn handle_analyze_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
220 let input_path = matches
221 .get_one::<String>("input")
222 .ok_or("Missing input path")?;
223 let output_path = matches
224 .get_one::<String>("output")
225 .ok_or("Missing output path")?;
226 let format = matches
227 .get_one::<String>("format")
228 .ok_or("Missing format")?;
229
230 tracing::info!("🔍 Analyzing existing memory snapshot");
231 tracing::info!("📄 Input: {}", input_path);
232 tracing::info!("📄 Output: {}", output_path);
233 tracing::info!("📊 Format: {}", format);
234
235 let result: Result<(), Box<dyn std::error::Error>> = Ok(());
237
238 match result {
239 Ok(()) => {
240 tracing::info!("✅ Analysis completed successfully");
241 tracing::info!("📊 Report generated: {}", output_path);
242 Ok(())
243 }
244 Err(e) => {
245 tracing::error!("❌ Analysis failed: {}", e);
246 Err(e)
247 }
248 }
249}
250
251fn handle_legacy_mode(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
252 let export_format = matches.get_one::<String>("export");
253 let output_path = matches
254 .get_one::<String>("output")
255 .ok_or("Missing output path")?;
256 let auto_track = matches.get_flag("auto-track");
257
258 if let Some(command_args) = matches.get_many::<String>("command") {
259 let command_args: Vec<&String> = command_args.collect();
260
261 tracing::info!("⚠️ Using legacy mode - consider using 'memscope run' instead");
262 tracing::info!(
263 "📋 Command: {}",
264 command_args
265 .iter()
266 .map(|s| s.as_str())
267 .collect::<Vec<_>>()
268 .join(" ")
269 );
270
271 if let Some(format) = export_format {
272 tracing::info!("📊 Export format: {}", format);
273 tracing::info!("📁 Output path: {}", output_path);
274 }
275
276 if auto_track {
277 tracing::info!("🔍 Auto-tracking enabled");
278 }
279
280 let mut env_vars = vec![("MEMSCOPE_ENABLED", "1"), ("MEMSCOPE_AUTO_EXPORT", "1")];
282
283 if auto_track {
284 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
285 }
286
287 if let Some(format) = export_format {
288 env_vars.push(("MEMSCOPE_EXPORT_FORMAT", format));
289 env_vars.push(("MEMSCOPE_EXPORT_PATH", output_path));
290 }
291
292 let result = execute_with_tracking(&command_args, &env_vars);
294
295 match result {
296 Ok(()) => {
297 tracing::info!("✅ Program execution completed successfully");
298
299 if export_format.is_some() {
300 tracing::info!("📊 Memory analysis exported to: {}", output_path);
301
302 }
305 Ok(())
306 }
307 Err(e) => {
308 tracing::error!("❌ Program execution failed: {}", e);
309 Err(e)
310 }
311 }
312 } else {
313 Err("No command provided. Use 'memscope run <command>' or 'memscope analyze <input> <output>'".into())
314 }
315}
316
317fn _analyze_existing_snapshot(
318 input_path: &str,
319 _output_path: &str,
320 format: &str,
321) -> Result<(), Box<dyn std::error::Error>> {
322 if !Path::new(input_path).exists() {
323 return Err(format!("Input file not found: {input_path}").into());
324 }
325
326 match format {
327 "html" => {
328 Err("HTML generation not implemented".into())
330 }
331 "svg" => {
332 Err("SVG generation not implemented".into())
334 }
335 "both" => {
336 Err("Both format generation not implemented".into())
338 }
339 _ => Err(format!("Unsupported format: {format}").into()),
340 }
341}
342
343fn generate_html_report(
344 input_path: &str,
345 output_path: &str,
346) -> Result<(), Box<dyn std::error::Error>> {
347 tracing::info!("🌐 Generating HTML report...");
348
349 let json_content = std::fs::read_to_string(input_path)?;
351
352 let html_content = format!(
354 "<!DOCTYPE html>\n<html>\n<head>\n <title>MemScope Analysis Report</title>\n <style>\n body {{ font-family: Arial, sans-serif; margin: 20px; }}\n .header {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}\n .section {{ margin: 20px 0; }}\n .data {{ background: #f9f9f9; padding: 10px; border-radius: 3px; }}\n </style>\n</head>\n<body>\n <div class=\"header\">\n <h1>🚀 MemScope Analysis Report</h1>\n <p>Generated from: {input_path}</p>\n </div>\n <div class=\"section\">\n <h2>📊 Memory Analysis Data</h2>\n <div class=\"data\">\n <pre>{json_content}</pre>\n </div>\n </div>\n</body>\n</html>",
355 );
356
357 std::fs::write(output_path, html_content)?;
358 Ok(())
359}
360
361fn generate_svg_visualization(
362 input_path: &str,
363 output_path: &str,
364) -> Result<(), Box<dyn std::error::Error>> {
365 tracing::info!("📈 Generating SVG visualization...");
366
367 let svg_content = format!(
369 "<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"800\" height=\"600\" fill=\"#f0f0f0\"/>\n <text x=\"400\" y=\"50\" text-anchor=\"middle\" font-size=\"24\" font-weight=\"bold\">MemScope Visualization</text>\n <text x=\"400\" y=\"80\" text-anchor=\"middle\" font-size=\"14\">Generated from: {input_path}</text>\n <text x=\"400\" y=\"300\" text-anchor=\"middle\" font-size=\"16\">SVG visualization would be generated here</text>\n</svg>",
370 );
371
372 std::fs::write(output_path, svg_content)?;
373 Ok(())
374}
375
376fn execute_with_tracking(
377 command_args: &[&String],
378 env_vars: &[(&str, &str)],
379) -> Result<(), Box<dyn std::error::Error>> {
380 if command_args.is_empty() {
381 return Err("No command provided".into());
382 }
383
384 let program = command_args[0];
385 let args = &command_args[1..];
386
387 tracing::info!(
388 "🔄 Executing: {} {}",
389 program,
390 args.iter()
391 .map(|s| s.as_str())
392 .collect::<Vec<_>>()
393 .join(" ")
394 );
395
396 let mut cmd = Command::new(program);
397 cmd.args(args);
398
399 for (key, value) in env_vars {
401 cmd.env(key, value);
402 tracing::info!("🔧 Setting env: {}={}", key, value);
403 }
404
405 cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
407
408 let status = cmd.status()?;
409
410 if !status.success() {
411 return Err(format!("Command failed with exit code: {:?}", status.code()).into());
412 }
413
414 tracing::info!("⏳ Waiting for cleanup to complete...");
417 std::thread::sleep(std::time::Duration::from_millis(200));
418
419 Ok(())
420}
421
422fn handle_run_command_wrapper(matches: &clap::ArgMatches) {
424 if let Err(e) = handle_run_command(matches) {
425 tracing::error!("❌ Run command failed: {}", e);
426 std::process::exit(1);
427 }
428}
429
430fn handle_analyze_command_wrapper(matches: &clap::ArgMatches) {
431 if let Err(e) = handle_analyze_command(matches) {
432 tracing::error!("❌ Analyze command failed: {}", e);
433 std::process::exit(1);
434 }
435}
436
437fn handle_legacy_mode_wrapper(matches: &clap::ArgMatches) {
438 if let Err(e) = handle_legacy_mode(matches) {
439 tracing::error!("❌ Legacy mode failed: {}", e);
440 std::process::exit(1);
441 }
442}
443
444fn _post_process_analysis(output_path: &str, format: &str) {
445 match format {
446 "json" => {
447 let json_path = format!("{output_path}.json");
448 if Path::new(&json_path).exists() {
449 tracing::info!("📄 JSON analysis: {}", json_path);
450 }
452 }
453 "html" => {
454 let html_path = format!("{output_path}.html");
455 if Path::new(&html_path).exists() {
456 tracing::info!("🌐 HTML dashboard: {}", html_path);
457 }
458 }
459 "both" => {
460 }
462 _ => {}
463 }
464}
465
466fn analyze_json_output(json_path: &str) {
467 if let Ok(content) = std::fs::read_to_string(json_path) {
469 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&content) {
470 if let Some(stats) = data
471 .get("memory_analysis")
472 .and_then(|ma| ma.get("statistics"))
473 .and_then(|s| s.get("lifecycle_analysis"))
474 {
475 tracing::info!("📈 Quick Analysis:");
476
477 if let Some(user_stats) = stats.get("user_allocations") {
478 if let Some(total) = user_stats.get("total_count") {
479 tracing::info!(" 👤 User allocations: {}", total);
480 }
481 if let Some(avg_lifetime) = user_stats.get("average_lifetime_ms") {
482 tracing::info!(" ⏱️ Average lifetime: {}ms", avg_lifetime);
483 }
484 }
485
486 if let Some(system_stats) = stats.get("system_allocations") {
487 if let Some(total) = system_stats.get("total_count") {
488 tracing::info!(" 🔧 System allocations: {}", total);
489 }
490 }
491 }
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use clap::{Arg, ArgMatches, Command as ClapCommand};
500 use std::fs;
501 use tempfile::TempDir;
502
503 fn create_test_matches_with_command(command: Vec<&str>) -> ArgMatches {
504 let command_strings: Vec<String> = command.iter().map(|s| s.to_string()).collect();
505 ClapCommand::new("test")
506 .arg(Arg::new("command").num_args(1..))
507 .arg(Arg::new("export").long("export"))
508 .arg(Arg::new("output").long("output"))
509 .try_get_matches_from(
510 std::iter::once("test".to_string())
511 .chain(command_strings)
512 .collect::<Vec<String>>(),
513 )
514 .expect("Failed to create test matches")
515 }
516
517 fn create_analyze_matches(input: &str, output: &str, format: &str) -> ArgMatches {
518 ClapCommand::new("test")
519 .arg(Arg::new("input"))
520 .arg(Arg::new("output"))
521 .arg(Arg::new("format").long("format"))
522 .try_get_matches_from(vec!["test", input, output, "--format", format])
523 .expect("Failed to create analyze matches")
524 }
525
526 #[test]
527 fn test_argument_extraction() {
528 let matches = create_test_matches_with_command(vec!["echo", "hello"]);
530
531 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
532
533 assert_eq!(command_args.len(), 2);
534 assert_eq!(command_args[0], "echo");
535 assert_eq!(command_args[1], "hello");
536 }
537
538 #[test]
539 fn test_default_values() {
540 let matches = create_test_matches_with_command(vec!["echo", "test"]);
542
543 let export_format = matches
544 .get_one::<String>("export")
545 .map(|s| s.as_str())
546 .unwrap_or("html");
547 let output_path = matches
548 .get_one::<String>("output")
549 .map(|s| s.as_str())
550 .unwrap_or("memory_analysis");
551
552 assert_eq!(export_format, "html");
553 assert_eq!(output_path, "memory_analysis");
554 }
555
556 #[test]
557 fn test_environment_variable_setup() {
558 let export_format = "json";
560 let output_path = "test_output";
561 let auto_track = true;
562 let wait_completion = false;
563
564 let mut env_vars = vec![
565 ("MEMSCOPE_ENABLED", "1"),
566 ("MEMSCOPE_AUTO_EXPORT", "1"),
567 ("MEMSCOPE_EXPORT_FORMAT", export_format),
568 ("MEMSCOPE_EXPORT_PATH", output_path),
569 ];
570
571 if auto_track {
572 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
573 }
574
575 if wait_completion {
576 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
577 }
578
579 assert_eq!(env_vars.len(), 5); assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
581 assert!(!env_vars
582 .iter()
583 .any(|(k, _)| *k == "MEMSCOPE_WAIT_COMPLETION"));
584 }
585
586 #[test]
587 fn test_command_validation() {
588 let empty_command: Vec<&String> = vec![];
590 let valid_command = ["echo".to_string(), "test".to_string()];
591 let valid_command_refs: Vec<&String> = valid_command.iter().collect();
592
593 assert!(empty_command.is_empty());
595
596 assert!(!valid_command_refs.is_empty());
598 assert_eq!(valid_command_refs[0], "echo");
599 assert_eq!(valid_command_refs[1], "test");
600 }
601
602 #[test]
603 fn test_generate_html_report() {
604 let temp_dir = TempDir::new().expect("Failed to create temp directory");
606 let input_path = temp_dir.path().join("test_input.json");
607 let output_path = temp_dir.path().join("test_output.html");
608
609 let test_json = r#"{"memory_analysis": {"statistics": {"total_allocations": 100}}}"#;
611 fs::write(&input_path, test_json).expect("Failed to write test JSON");
612
613 let result =
615 generate_html_report(input_path.to_str().unwrap(), output_path.to_str().unwrap());
616
617 assert!(result.is_ok());
618 assert!(output_path.exists());
619
620 let html_content = fs::read_to_string(&output_path).expect("Failed to read HTML");
622 assert!(html_content.contains("<!DOCTYPE html>"));
623 assert!(html_content.contains("MemScope Analysis Report"));
624 }
625
626 #[test]
627 fn test_generate_svg_visualization() {
628 let temp_dir = TempDir::new().expect("Failed to create temp directory");
630 let input_path = temp_dir.path().join("test_input.json");
631 let output_path = temp_dir.path().join("test_output.svg");
632
633 fs::write(&input_path, "{}").expect("Failed to write test file");
635
636 let result =
638 generate_svg_visualization(input_path.to_str().unwrap(), output_path.to_str().unwrap());
639
640 assert!(result.is_ok());
641 assert!(output_path.exists());
642
643 let svg_content = fs::read_to_string(&output_path).expect("Failed to read SVG");
645 assert!(svg_content.contains("<svg"));
646 assert!(svg_content.contains("MemScope Visualization"));
647 }
648
649 #[test]
650 fn test_handle_analyze_command() {
651 let temp_dir = TempDir::new().expect("Failed to create temp directory");
653 let input_path = temp_dir.path().join("input.json");
654 let output_path = temp_dir.path().join("output.html");
655
656 fs::write(&input_path, "{}").expect("Failed to write test file");
658
659 let matches = create_analyze_matches(
660 input_path.to_str().unwrap(),
661 output_path.to_str().unwrap(),
662 "html",
663 );
664
665 let result = handle_analyze_command(&matches);
666 assert!(result.is_ok());
667 }
668
669 #[test]
670 fn test_analyze_json_output_parsing() {
671 let temp_dir = TempDir::new().expect("Failed to create temp directory");
673 let json_path = temp_dir.path().join("test_analysis.json");
674
675 let test_json = r#"{
677 "memory_analysis": {
678 "statistics": {
679 "lifecycle_analysis": {
680 "user_allocations": {
681 "total_count": 150,
682 "average_lifetime_ms": 250
683 },
684 "system_allocations": {
685 "total_count": 75
686 }
687 }
688 }
689 }
690 }"#;
691
692 fs::write(&json_path, test_json).expect("Failed to write test JSON");
693
694 analyze_json_output(json_path.to_str().unwrap());
696
697 assert!(json_path.exists());
699 let content = fs::read_to_string(&json_path).expect("Failed to read JSON");
700 let data: serde_json::Value = serde_json::from_str(&content).expect("Invalid JSON");
701
702 assert!(data.get("memory_analysis").is_some());
703 }
704
705 #[test]
706 fn test_format_validation() {
707 let valid_formats = ["json", "html", "both", "svg"];
709 let invalid_formats = ["xml", "csv", "txt"];
710
711 for format in valid_formats {
712 assert!(["json", "html", "both", "svg"].contains(&format));
714 }
715
716 for format in invalid_formats {
717 assert!(!["json", "html", "both", "svg"].contains(&format));
719 }
720 }
721
722 #[test]
723 fn test_path_handling() {
724 let temp_dir = TempDir::new().expect("Failed to create temp directory");
726 let valid_path = temp_dir.path().join("valid_file.json");
727 let invalid_path = temp_dir
728 .path()
729 .join("nonexistent")
730 .join("invalid_file.json");
731
732 fs::write(&valid_path, "{}").expect("Failed to write test file");
734 assert!(valid_path.exists());
735
736 assert!(!invalid_path.exists());
738 assert!(!invalid_path.parent().unwrap().exists());
739 }
740
741 #[test]
742 fn test_command_args_processing() {
743 let test_cases = vec![
745 (vec!["echo", "hello"], "echo hello"),
746 (vec!["cargo", "run", "--release"], "cargo run --release"),
747 (vec!["ls", "-la"], "ls -la"),
748 ];
749
750 for (args, expected) in test_cases {
751 let joined = args.join(" ");
752 assert_eq!(joined, expected);
753
754 let program = args[0];
756 let remaining_args = &args[1..];
757
758 assert_eq!(program, args[0]);
759 assert_eq!(remaining_args.len(), args.len() - 1);
760 }
761 }
762
763 #[test]
764 fn test_error_handling() {
765 let empty_command: Vec<&String> = vec![];
769 assert!(empty_command.is_empty());
770
771 let matches = ClapCommand::new("test")
773 .arg(Arg::new("input"))
774 .arg(Arg::new("output"))
775 .try_get_matches_from(vec!["test"])
776 .unwrap();
777
778 let missing_input = matches.get_one::<String>("input");
779 let missing_output = matches.get_one::<String>("output");
780
781 assert!(missing_input.is_none());
782 assert!(missing_output.is_none());
783 }
784
785 #[test]
786 fn test_legacy_mode_detection() {
787 let matches_with_export = ClapCommand::new("test")
789 .arg(Arg::new("export").long("export"))
790 .arg(Arg::new("command").num_args(1..))
791 .try_get_matches_from(vec!["test", "--export", "json", "echo", "test"])
792 .unwrap();
793
794 let has_export = matches_with_export.get_one::<String>("export").is_some();
795 let has_command = matches_with_export.get_many::<String>("command").is_some();
796
797 assert!(has_export);
798 assert!(has_command);
799
800 let is_legacy_mode = has_export && has_command;
802 assert!(is_legacy_mode);
803 }
804
805 #[test]
806 fn test_run_analyze_with_valid_args() {
807 let matches = ClapCommand::new("test")
809 .arg(Arg::new("command").num_args(1..))
810 .arg(Arg::new("export").long("export").default_value("html"))
811 .arg(
812 Arg::new("output")
813 .long("output")
814 .default_value("memory_analysis"),
815 )
816 .try_get_matches_from(vec!["test", "echo", "hello"])
817 .unwrap();
818
819 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
821 let export_format = matches
822 .get_one::<String>("export")
823 .map(|s| s.as_str())
824 .unwrap_or("html");
825 let output_path = matches
826 .get_one::<String>("output")
827 .map(|s| s.as_str())
828 .unwrap_or("memory_analysis");
829
830 assert_eq!(command_args.len(), 2);
831 assert_eq!(command_args[0], "echo");
832 assert_eq!(command_args[1], "hello");
833 assert_eq!(export_format, "html");
834 assert_eq!(output_path, "memory_analysis");
835 }
836
837 #[test]
838 fn test_run_analyze_missing_command() {
839 let matches = ClapCommand::new("test")
841 .arg(Arg::new("command").num_args(1..))
842 .try_get_matches_from(vec!["test"])
843 .unwrap();
844
845 let command_result = matches.get_many::<String>("command");
846 assert!(command_result.is_none());
847 }
848
849 #[test]
850 fn test_handle_run_command_logic() {
851 let matches = ClapCommand::new("test")
853 .arg(Arg::new("command").num_args(1..))
854 .arg(Arg::new("export").long("export"))
855 .arg(Arg::new("output").long("output"))
856 .arg(
857 Arg::new("auto-track")
858 .long("auto-track")
859 .action(clap::ArgAction::SetTrue),
860 )
861 .arg(
862 Arg::new("wait-completion")
863 .long("wait-completion")
864 .action(clap::ArgAction::SetTrue),
865 )
866 .try_get_matches_from(vec![
867 "test",
868 "echo",
869 "test",
870 "--export",
871 "json",
872 "--output",
873 "test_output",
874 "--auto-track",
875 "--wait-completion",
876 ])
877 .unwrap();
878
879 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
880 let export_format = matches.get_one::<String>("export").unwrap();
881 let output_path = matches.get_one::<String>("output").unwrap();
882 let auto_track = matches.get_flag("auto-track");
883 let wait_completion = matches.get_flag("wait-completion");
884
885 assert_eq!(command_args.len(), 2);
886 assert_eq!(export_format, "json");
887 assert_eq!(output_path, "test_output");
888 assert!(auto_track);
889 assert!(wait_completion);
890
891 let mut env_vars = vec![
893 ("MEMSCOPE_ENABLED", "1"),
894 ("MEMSCOPE_AUTO_EXPORT", "1"),
895 ("MEMSCOPE_EXPORT_FORMAT", export_format),
896 ("MEMSCOPE_EXPORT_PATH", output_path),
897 ];
898
899 if auto_track {
900 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
901 }
902
903 if wait_completion {
904 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
905 }
906
907 assert_eq!(env_vars.len(), 6);
908 assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
909 assert!(env_vars.contains(&("MEMSCOPE_WAIT_COMPLETION", "1")));
910 }
911
912 #[test]
913 fn test_handle_legacy_mode_logic() {
914 let matches = ClapCommand::new("test")
916 .arg(Arg::new("export").long("export"))
917 .arg(
918 Arg::new("output")
919 .long("output")
920 .default_value("default_output"),
921 )
922 .arg(
923 Arg::new("auto-track")
924 .long("auto-track")
925 .action(clap::ArgAction::SetTrue),
926 )
927 .arg(Arg::new("command").num_args(1..))
928 .try_get_matches_from(vec![
929 "test",
930 "--export",
931 "html",
932 "--output",
933 "legacy_output",
934 "--auto-track",
935 "cargo",
936 "run",
937 ])
938 .unwrap();
939
940 let export_format = matches.get_one::<String>("export");
941 let output_path = matches.get_one::<String>("output").unwrap();
942 let auto_track = matches.get_flag("auto-track");
943 let command_args: Option<clap::parser::ValuesRef<String>> =
944 matches.get_many::<String>("command");
945
946 assert!(export_format.is_some());
947 assert_eq!(export_format.unwrap(), "html");
948 assert_eq!(output_path, "legacy_output");
949 assert!(auto_track);
950 assert!(command_args.is_some());
951
952 let command_vec: Vec<&String> = command_args.unwrap().collect();
953 assert_eq!(command_vec.len(), 2);
954 assert_eq!(command_vec[0], "cargo");
955 assert_eq!(command_vec[1], "run");
956 }
957
958 #[test]
959 fn test_analyze_existing_snapshot_logic() {
960 let temp_dir = TempDir::new().expect("Failed to create temp directory");
962 let existing_file = temp_dir.path().join("existing.json");
963 let nonexistent_file = temp_dir.path().join("nonexistent.json");
964
965 fs::write(&existing_file, "{}").expect("Failed to write test file");
967
968 assert!(existing_file.exists());
970
971 assert!(!nonexistent_file.exists());
973
974 let formats = ["html", "svg", "both", "invalid"];
976 for format in formats {
977 match format {
978 "html" | "svg" | "both" => {
979 assert!(["html", "svg", "both"].contains(&format));
981 }
982 _ => {
983 assert!(!["html", "svg", "both"].contains(&format));
985 }
986 }
987 }
988 }
989
990 #[test]
991 fn test_execute_with_tracking_validation() {
992 let empty_command: Vec<&String> = vec![];
994 let valid_command = ["echo".to_string(), "hello".to_string()];
995 let valid_refs: Vec<&String> = valid_command.iter().collect();
996
997 assert!(empty_command.is_empty());
999
1000 assert!(!valid_refs.is_empty());
1002 let program = valid_refs[0];
1003 let args = &valid_refs[1..];
1004
1005 assert_eq!(program, "echo");
1006 assert_eq!(args.len(), 1);
1007 assert_eq!(args[0], "hello");
1008
1009 let env_vars = [
1011 ("MEMSCOPE_ENABLED", "1"),
1012 ("MEMSCOPE_AUTO_EXPORT", "1"),
1013 ("TEST_VAR", "test_value"),
1014 ];
1015
1016 for (key, value) in env_vars {
1017 assert!(!key.is_empty());
1018 assert!(!value.is_empty());
1019 }
1020 }
1021
1022 #[test]
1023 fn test_html_content_generation() {
1024 let input_path = "test_input.json";
1026 let expected_elements = [
1027 "<!DOCTYPE html>",
1028 "<html>",
1029 "<head>",
1030 "<title>MemScope Analysis Report</title>",
1031 "<style>",
1032 "body { font-family: Arial, sans-serif; margin: 20px; }",
1033 ".header { background: #f0f0f0; padding: 20px; border-radius: 5px; }",
1034 "</head>",
1035 "<body>",
1036 "<h1>🚀 MemScope Analysis Report</h1>",
1037 ];
1038
1039 let html_start = format!(
1041 "<!DOCTYPE html>\n<html>\n<head>\n <title>MemScope Analysis Report</title>\n <style>\n body {{ font-family: Arial, sans-serif; margin: 20px; }}\n .header {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}\n .section {{ margin: 20px 0; }}\n .data {{ background: #f9f9f9; padding: 10px; border-radius: 3px; }}\n </style>\n</head>\n<body>\n <div class=\"header\">\n <h1>🚀 MemScope Analysis Report</h1>\n <p>Generated from: {}",
1042 input_path
1043 );
1044
1045 for element in expected_elements {
1046 if !element.contains("Generated from") {
1047 assert!(html_start.contains(element));
1048 }
1049 }
1050 }
1051
1052 #[test]
1053 fn test_svg_content_generation() {
1054 let input_path = "test_input.json";
1056 let svg_content = format!(
1057 "<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"800\" height=\"600\" fill=\"#f0f0f0\"/>\n <text x=\"400\" y=\"50\" text-anchor=\"middle\" font-size=\"24\" font-weight=\"bold\">MemScope Visualization</text>\n <text x=\"400\" y=\"80\" text-anchor=\"middle\" font-size=\"14\">Generated from: {}</text>\n <text x=\"400\" y=\"300\" text-anchor=\"middle\" font-size=\"16\">SVG visualization would be generated here</text>\n</svg>",
1058 input_path
1059 );
1060
1061 let expected_elements = [
1062 "<svg",
1063 "width=\"800\"",
1064 "height=\"600\"",
1065 "xmlns=\"http://www.w3.org/2000/svg\"",
1066 "<rect",
1067 "fill=\"#f0f0f0\"",
1068 "<text",
1069 "MemScope Visualization",
1070 "Generated from:",
1071 "</svg>",
1072 ];
1073
1074 for element in expected_elements {
1075 assert!(svg_content.contains(element));
1076 }
1077 }
1078
1079 #[test]
1080 fn test_command_string_joining() {
1081 let test_cases = vec![
1083 (vec!["echo", "hello"], "echo hello"),
1084 (vec!["cargo", "run", "--release"], "cargo run --release"),
1085 (vec!["ls", "-la", "/tmp"], "ls -la /tmp"),
1086 (
1087 vec!["git", "commit", "-m", "test message"],
1088 "git commit -m test message",
1089 ),
1090 ];
1091
1092 for (args, expected) in test_cases {
1093 let joined = args.to_vec().join(" ");
1094 assert_eq!(joined, expected);
1095 }
1096 }
1097
1098 #[test]
1099 fn test_flag_combinations() {
1100 let matches = ClapCommand::new("test")
1102 .arg(
1103 Arg::new("auto-track")
1104 .long("auto-track")
1105 .action(clap::ArgAction::SetTrue),
1106 )
1107 .arg(
1108 Arg::new("wait-completion")
1109 .long("wait-completion")
1110 .action(clap::ArgAction::SetTrue),
1111 )
1112 .arg(
1113 Arg::new("verbose")
1114 .long("verbose")
1115 .action(clap::ArgAction::SetTrue),
1116 )
1117 .try_get_matches_from(vec!["test", "--auto-track", "--verbose"])
1118 .unwrap();
1119
1120 let auto_track = matches.get_flag("auto-track");
1121 let wait_completion = matches.get_flag("wait-completion");
1122 let verbose = matches.get_flag("verbose");
1123
1124 assert!(auto_track);
1125 assert!(!wait_completion);
1126 assert!(verbose);
1127
1128 let mut env_count = 0;
1130 if auto_track {
1131 env_count += 1;
1132 }
1133 if wait_completion {
1134 env_count += 1;
1135 }
1136 if verbose {
1137 env_count += 1;
1138 }
1139
1140 assert_eq!(env_count, 2); }
1142
1143 #[test]
1144 fn test_error_message_formatting() {
1145 let test_errors = vec![
1147 ("Missing command arguments", "Missing command arguments"),
1148 ("Missing export format", "Missing export format"),
1149 ("Missing output path", "Missing output path"),
1150 (
1151 "Command failed with exit code: Some(1)",
1152 "Command failed with exit code: Some(1)",
1153 ),
1154 ];
1155
1156 for (error_msg, expected) in test_errors {
1157 assert_eq!(error_msg, expected);
1158 assert!(!error_msg.is_empty());
1159 }
1160
1161 let exit_code = Some(1);
1163 let formatted_error = format!("Command failed with exit code: {:?}", exit_code);
1164 assert_eq!(formatted_error, "Command failed with exit code: Some(1)");
1165 }
1166
1167 #[test]
1168 fn test_file_extension_handling() {
1169 let test_paths = vec![
1171 ("output.html", "html"),
1172 ("output.svg", "svg"),
1173 ("output.json", "json"),
1174 ("output", ""),
1175 ];
1176
1177 for (path, expected_ext) in test_paths {
1178 let extension = path.split('.').next_back().unwrap_or("");
1179 if path.contains('.') {
1180 assert_eq!(extension, expected_ext);
1181 } else {
1182 assert_eq!(extension, path);
1183 }
1184 }
1185 }
1186
1187 #[test]
1188 fn test_timeout_duration() {
1189 let timeout_ms = 200;
1191 let duration = std::time::Duration::from_millis(timeout_ms);
1192
1193 assert_eq!(duration.as_millis(), 200);
1194 assert!(duration.as_millis() > 0);
1195 assert!(duration.as_millis() < 1000); }
1197}