memscope_rs/cli/commands/
analyze.rs

1//! Memory analysis command implementation
2//!
3//! This module provides the analyze subcommand functionality.
4
5#![allow(dead_code)]
6use clap::ArgMatches;
7use std::error::Error;
8use std::path::Path;
9use std::process::{Command, Stdio};
10
11/// Run the analyze command
12pub fn run_analyze(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
13    // Extract arguments from matches
14    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    // Initialize memory tracking
33    crate::init();
34
35    // Execute the command with memory tracking
36    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        // Legacy mode (backward compatibility)
100        .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            // Legacy run command - functionality moved to main analyze command
133            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            // Legacy mode for backward compatibility
143            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    // Set environment variables for the target process
185    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    // Execute the target command with memory tracking
201    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            // Post-process the exported data
209            // Post-processing would happen here if needed
210            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    // Legacy snapshot analysis - not implemented
236    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        // Set environment variables for the target process
281        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        // Execute the target command with memory tracking
293        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                    // Post-process the exported data if needed
303                    // Post-processing would happen here if needed
304                }
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            // Generate HTML report from JSON - not implemented
329            Err("HTML generation not implemented".into())
330        }
331        "svg" => {
332            // Generate SVG visualization from JSON - not implemented
333            Err("SVG generation not implemented".into())
334        }
335        "both" => {
336            // Both HTML and SVG generation - not implemented
337            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    // Read the JSON data
350    let json_content = std::fs::read_to_string(input_path)?;
351
352    // Create HTML content
353    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    // Create SVG content
368    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    // Set environment variables for memory tracking
400    for (key, value) in env_vars {
401        cmd.env(key, value);
402        tracing::info!("🔧 Setting env: {}={}", key, value);
403    }
404
405    // Inherit stdio to see the program output
406    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    // Give some time for all Drop destructors to complete
415    // This is crucial for TrackedVariable Drop implementations to finish
416    tracing::info!("⏳ Waiting for cleanup to complete...");
417    std::thread::sleep(std::time::Duration::from_millis(200));
418
419    Ok(())
420}
421
422// Update function calls to handle Results
423fn 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                // JSON analysis would happen here
451            }
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            // Both JSON and HTML post-processing would happen here
461        }
462        _ => {}
463    }
464}
465
466fn analyze_json_output(json_path: &str) {
467    // Quick analysis of the exported JSON
468    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        // Test argument extraction from ArgMatches
529        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        // Test default value handling
541        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        // Test environment variable setup logic
559        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); // 4 base + 1 auto_track
580        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        // Test command validation logic
589        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        // Test empty command
594        assert!(empty_command.is_empty());
595
596        // Test valid command
597        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        // Test HTML report generation
605        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        // Create test JSON input
610        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        // Test HTML generation
614        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        // Verify HTML content
621        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        // Test SVG visualization generation
629        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        // Create test input file
634        fs::write(&input_path, "{}").expect("Failed to write test file");
635
636        // Test SVG generation
637        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        // Verify SVG content
644        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        // Test analyze command handling
652        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        // Create test input file
657        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        // Test JSON output analysis logic
672        let temp_dir = TempDir::new().expect("Failed to create temp directory");
673        let json_path = temp_dir.path().join("test_analysis.json");
674
675        // Create test JSON with expected structure
676        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        // Test JSON parsing (function doesn't return value, but should not panic)
695        analyze_json_output(json_path.to_str().unwrap());
696
697        // Verify file exists and is readable
698        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        // Test format validation logic
708        let valid_formats = ["json", "html", "both", "svg"];
709        let invalid_formats = ["xml", "csv", "txt"];
710
711        for format in valid_formats {
712            // These formats should be handled
713            assert!(["json", "html", "both", "svg"].contains(&format));
714        }
715
716        for format in invalid_formats {
717            // These formats should not be in valid list
718            assert!(!["json", "html", "both", "svg"].contains(&format));
719        }
720    }
721
722    #[test]
723    fn test_path_handling() {
724        // Test path handling and validation
725        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        // Test valid path
733        fs::write(&valid_path, "{}").expect("Failed to write test file");
734        assert!(valid_path.exists());
735
736        // Test invalid path
737        assert!(!invalid_path.exists());
738        assert!(!invalid_path.parent().unwrap().exists());
739    }
740
741    #[test]
742    fn test_command_args_processing() {
743        // Test command arguments processing
744        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            // Test argument splitting
755            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        // Test error handling scenarios
766
767        // Test missing command
768        let empty_command: Vec<&String> = vec![];
769        assert!(empty_command.is_empty());
770
771        // Test missing required arguments
772        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        // Test legacy mode detection logic
788        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        // This would indicate legacy mode usage
801        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        // Test run_analyze function with valid arguments
808        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        // This should not panic and should extract arguments correctly
820        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        // Test run_analyze function with missing command
840        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        // Test the logic inside handle_run_command without actually executing
852        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        // Test environment variable setup
892        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        // Test handle_legacy_mode logic without executing
915        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        // Test _analyze_existing_snapshot function logic
961        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        // Create existing file
966        fs::write(&existing_file, "{}").expect("Failed to write test file");
967
968        // Test with existing file
969        assert!(existing_file.exists());
970
971        // Test with nonexistent file
972        assert!(!nonexistent_file.exists());
973
974        // Test format handling logic
975        let formats = ["html", "svg", "both", "invalid"];
976        for format in formats {
977            match format {
978                "html" | "svg" | "both" => {
979                    // These should be handled (though not implemented)
980                    assert!(["html", "svg", "both"].contains(&format));
981                }
982                _ => {
983                    // Invalid format
984                    assert!(!["html", "svg", "both"].contains(&format));
985                }
986            }
987        }
988    }
989
990    #[test]
991    fn test_execute_with_tracking_validation() {
992        // Test execute_with_tracking input validation
993        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        // Test empty command validation
998        assert!(empty_command.is_empty());
999
1000        // Test valid command structure
1001        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        // Test environment variables setup
1010        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        // Test HTML content generation logic
1025        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        // Simulate HTML content creation (partial)
1040        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        // Test SVG content generation logic
1055        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        // Test command string joining logic used in logging
1082        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        // Test various flag combinations
1101        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        // Test flag-based environment variable setup
1129        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); // auto_track + verbose
1141    }
1142
1143    #[test]
1144    fn test_error_message_formatting() {
1145        // Test error message formatting
1146        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        // Test error formatting with dynamic content
1162        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        // Test file extension handling logic
1170        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        // Test timeout duration logic
1190        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); // Less than 1 second
1196    }
1197}