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    // Extract unified backend options
28    let tracking_mode = matches
29        .get_one::<String>("mode")
30        .map(|s| s.as_str())
31        .unwrap_or("auto");
32    let strategy = matches
33        .get_one::<String>("strategy")
34        .map(|s| s.as_str())
35        .unwrap_or("auto");
36    let sample_rate = matches
37        .get_one::<f64>("sample-rate")
38        .copied()
39        .unwrap_or(1.0);
40
41    tracing::info!("🔍 Starting memory analysis...");
42    tracing::info!("Command: {:?}", command_args);
43    tracing::info!("Tracking mode: {}", tracking_mode);
44    tracing::info!("Strategy: {}", strategy);
45    tracing::info!("Sample rate: {:.2}", sample_rate);
46    tracing::info!("Export format: {}", export_format);
47    tracing::info!("Output path: {}", output_path);
48
49    // Route to appropriate tracking system based on mode
50    match tracking_mode {
51        "unified" => {
52            run_unified_analysis(
53                &command_args,
54                strategy,
55                sample_rate,
56                export_format,
57                output_path,
58            )?;
59        }
60        "legacy" => {
61            run_legacy_analysis(&command_args, export_format, output_path)?;
62        }
63        "auto" => {
64            // Auto-detect best mode based on environment
65            if should_use_unified_backend(&command_args) {
66                tracing::info!("🤖 Auto-detected: Using unified backend");
67                run_unified_analysis(
68                    &command_args,
69                    strategy,
70                    sample_rate,
71                    export_format,
72                    output_path,
73                )?;
74            } else {
75                tracing::info!("🤖 Auto-detected: Using legacy backend");
76                run_legacy_analysis(&command_args, export_format, output_path)?;
77            }
78        }
79        _ => {
80            return Err(format!("Unsupported tracking mode: {}", tracking_mode).into());
81        }
82    }
83
84    Ok(())
85}
86
87/// Run unified backend analysis
88fn run_unified_analysis(
89    command_args: &[&String],
90    strategy: &str,
91    sample_rate: f64,
92    export_format: &str,
93    output_path: &str,
94) -> Result<(), Box<dyn Error>> {
95    use crate::unified::{detect_environment, BackendConfig, UnifiedBackend};
96
97    tracing::info!("🔧 Initializing unified backend...");
98
99    // Detect environment for strategy selection
100    let detected_environment = detect_environment()?;
101    tracing::info!("🌍 Environment detected: {:?}", detected_environment);
102
103    // Build backend configuration
104    let config = BackendConfig {
105        auto_detect: strategy == "auto",
106        force_strategy: None,
107        sample_rate,
108        max_overhead_percent: 5.0,
109    };
110
111    // Initialize unified backend
112    let mut backend = UnifiedBackend::initialize(config)?;
113
114    // Start tracking session
115    let session = backend.start_tracking()?;
116    tracing::info!(
117        "✅ Unified tracking session started: {}",
118        session.session_id()
119    );
120
121    // Execute command with tracking
122    let result = execute_with_unified_tracking(command_args, session.session_id())?;
123
124    // Collect tracking data
125    let analysis_data = backend.collect_data()?;
126    let tracking_data = analysis_data.raw_data;
127
128    tracing::info!(
129        "📊 Analysis completed. Collected {} bytes of data",
130        tracking_data.len()
131    );
132
133    // Export results
134    export_unified_results(&tracking_data, export_format, output_path)?;
135
136    if result.success() {
137        tracing::info!("🎉 Unified analysis completed successfully");
138        Ok(())
139    } else {
140        Err(format!("Command failed with exit code: {:?}", result.code()).into())
141    }
142}
143
144/// Run legacy backend analysis
145fn run_legacy_analysis(
146    command_args: &[&String],
147    export_format: &str,
148    output_path: &str,
149) -> Result<(), Box<dyn Error>> {
150    tracing::info!("🔧 Initializing legacy backend...");
151
152    // Initialize legacy memory tracking
153    crate::init();
154
155    // Execute command with legacy tracking
156    execute_with_tracking(command_args, &[])?;
157
158    tracing::info!("📊 Legacy analysis completed");
159    tracing::info!("💾 Results exported to: {}.{}", output_path, export_format);
160
161    Ok(())
162}
163
164/// Determine if unified backend should be used based on command analysis
165fn should_use_unified_backend(command_args: &[&String]) -> bool {
166    if command_args.is_empty() {
167        return false;
168    }
169
170    let command = command_args[0].as_str();
171
172    // Check for async-heavy programs
173    if command.contains("tokio") || command.contains("async-std") {
174        tracing::debug!("Detected async runtime, recommending unified backend");
175        return true;
176    }
177
178    // Check for multi-threaded programs
179    if command_args
180        .iter()
181        .any(|arg| arg.contains("--jobs") || arg.contains("-j") || arg.contains("parallel"))
182    {
183        tracing::debug!("Detected multi-threading hints, recommending unified backend");
184        return true;
185    }
186
187    // Check for cargo commands that might benefit from unified tracking
188    if command == "cargo" && command_args.len() > 1 {
189        let subcommand = command_args[1].as_str();
190        match subcommand {
191            "test" | "bench" | "run" => {
192                tracing::debug!(
193                    "Detected cargo {}, recommending unified backend",
194                    subcommand
195                );
196                return true;
197            }
198            _ => {}
199        }
200    }
201
202    // Default to legacy for simple commands
203    tracing::debug!("No unified backend indicators found, using legacy");
204    false
205}
206
207/// Execute command with unified tracking integration
208fn execute_with_unified_tracking(
209    command_args: &[&String],
210    session_id: &str,
211) -> Result<std::process::ExitStatus, Box<dyn Error>> {
212    if command_args.is_empty() {
213        return Err("No command provided".into());
214    }
215
216    let program = command_args[0];
217    let args = &command_args[1..];
218
219    let mut cmd = Command::new(program);
220    cmd.args(args);
221    cmd.stdout(Stdio::inherit());
222    cmd.stderr(Stdio::inherit());
223
224    // Set environment variables for unified tracking
225    cmd.env("MEMSCOPE_UNIFIED_ENABLED", "1");
226    cmd.env("MEMSCOPE_SESSION_ID", session_id);
227    cmd.env("MEMSCOPE_AUTO_EXPORT", "1");
228
229    let args_str = args
230        .iter()
231        .map(|s| s.as_str())
232        .collect::<Vec<_>>()
233        .join(" ");
234    tracing::info!("🔄 Executing: {} {}", program, args_str);
235
236    let status = cmd.status()?;
237    Ok(status)
238}
239
240/// Export unified backend results in the requested format
241fn export_unified_results(
242    tracking_data: &[u8],
243    export_format: &str,
244    output_path: &str,
245) -> Result<(), Box<dyn Error>> {
246    match export_format {
247        "json" => {
248            let json_path = format!("{}.json", output_path);
249            std::fs::write(&json_path, tracking_data)?;
250            tracing::info!("✅ JSON export completed: {}", json_path);
251        }
252        "html" => {
253            let html_path = format!("{}.html", output_path);
254            // Enhanced HTML generation with unified backend data
255            let html_content = generate_unified_html_report(tracking_data)?;
256            std::fs::write(&html_path, html_content)?;
257            tracing::info!("✅ HTML export completed: {}", html_path);
258        }
259        "svg" => {
260            let svg_path = format!("{}.svg", output_path);
261            // Generate SVG visualization (placeholder)
262            let svg_content = generate_unified_svg_visualization(tracking_data)?;
263            std::fs::write(&svg_path, svg_content)?;
264            tracing::info!("✅ SVG export completed: {}", svg_path);
265        }
266        _ => {
267            tracing::warn!(
268                "Unsupported export format: {}, defaulting to JSON",
269                export_format
270            );
271            let json_path = format!("{}.json", output_path);
272            std::fs::write(&json_path, tracking_data)?;
273        }
274    }
275
276    Ok(())
277}
278
279/// Generate enhanced HTML report for unified backend data
280fn generate_unified_html_report(tracking_data: &[u8]) -> Result<String, Box<dyn Error>> {
281    let html_content = format!(
282        r#"<!DOCTYPE html>
283<html lang="en">
284<head>
285    <meta charset="UTF-8">
286    <meta name="viewport" content="width=device-width, initial-scale=1.0">
287    <title>Unified Memory Analysis Report</title>
288    <style>
289        body {{ font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }}
290        .container {{ max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
291        .header {{ text-align: center; color: #333; border-bottom: 2px solid #007acc; padding-bottom: 10px; }}
292        .metric {{ display: inline-block; margin: 10px; padding: 15px; background: #e8f4ff; border-radius: 5px; }}
293        .metric-label {{ font-weight: bold; color: #007acc; }}
294        .metric-value {{ font-size: 1.2em; color: #333; }}
295    </style>
296</head>
297<body>
298    <div class="container">
299        <div class="header">
300            <h1>🔍 Unified Memory Analysis Report</h1>
301            <p>Generated with MemScope Unified Backend</p>
302        </div>
303        <div class="metrics">
304            <div class="metric">
305                <div class="metric-label">Data Collected</div>
306                <div class="metric-value">{} bytes</div>
307            </div>
308            <div class="metric">
309                <div class="metric-label">Backend Type</div>
310                <div class="metric-value">Unified</div>
311            </div>
312            <div class="metric">
313                <div class="metric-label">Analysis Status</div>
314                <div class="metric-value">✅ Complete</div>
315            </div>
316        </div>
317        <div class="content">
318            <h2>📊 Analysis Summary</h2>
319            <p>This report was generated using the unified memory tracking backend, providing comprehensive analysis across single-threaded, multi-threaded, and async execution contexts.</p>
320            <h3>🚀 Features Used</h3>
321            <ul>
322                <li>Intelligent strategy selection</li>
323                <li>Cross-context memory tracking</li>
324                <li>Performance-optimized data collection</li>
325                <li>Unified export format</li>
326            </ul>
327        </div>
328    </div>
329</body>
330</html>"#,
331        tracking_data.len()
332    );
333
334    Ok(html_content)
335}
336
337/// Generate SVG visualization for unified backend data
338fn generate_unified_svg_visualization(tracking_data: &[u8]) -> Result<String, Box<dyn Error>> {
339    let data_size = tracking_data.len();
340    let svg_content = format!(
341        r#"<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
342            <rect width="800" height="600" fill="{}"/>
343            <text x="400" y="50" text-anchor="middle" font-family="Arial" font-size="24" fill="{}">
344                Unified Memory Analysis
345            </text>
346            <text x="400" y="80" text-anchor="middle" font-family="Arial" font-size="14" fill="{}">
347                Data Size: {} bytes
348            </text>
349            <rect x="100" y="150" width="600" height="300" fill="{}" stroke="{}" stroke-width="2"/>
350            <text x="400" y="320" text-anchor="middle" font-family="Arial" font-size="16" fill="{}">
351                Unified Backend Analysis Complete
352            </text>
353        </svg>"#,
354        "#f8f9fa", "#333", "#666", data_size, "#e8f4fd", "#1976d2", "#1976d2"
355    );
356
357    Ok(svg_content)
358}
359
360fn _original_main() {
361    use clap::{Arg, Command as ClapCommand};
362    let matches = ClapCommand::new("memscope")
363        .version("0.1.0")
364        .author("MemScope Team")
365        .about("Memory analysis tool for Rust programs")
366        .subcommand(
367            ClapCommand::new("run")
368                .about("Run and track program memory")
369                .arg(
370                    Arg::new("command")
371                        .help("Command to run (e.g., 'cargo run --release')")
372                        .required(true)
373                        .num_args(1..),
374                )
375                .arg(
376                    Arg::new("export")
377                        .long("export")
378                        .value_name("FORMAT")
379                        .help("Export format: json, html, or both")
380                        .value_parser(["json", "html", "both"])
381                        .default_value("json"),
382                )
383                .arg(
384                    Arg::new("output")
385                        .short('o')
386                        .long("output")
387                        .value_name("PATH")
388                        .help("Output file path (without extension)")
389                        .default_value("memscope_analysis"),
390                )
391                .arg(
392                    Arg::new("auto-track")
393                        .long("auto-track")
394                        .help("Automatically track all allocations")
395                        .action(clap::ArgAction::SetTrue),
396                )
397                .arg(
398                    Arg::new("wait-completion")
399                        .long("wait-completion")
400                        .help("Wait for program completion before exporting")
401                        .action(clap::ArgAction::SetTrue),
402                ),
403        )
404        .subcommand(
405            ClapCommand::new("analyze")
406                .about("Analyze existing memory snapshot")
407                .arg(Arg::new("input").help("Input JSON file").required(true))
408                .arg(Arg::new("output").help("Output HTML file").required(true))
409                .arg(
410                    Arg::new("format")
411                        .long("format")
412                        .value_name("FORMAT")
413                        .help("Output format: html, svg, or both")
414                        .value_parser(["html", "svg", "both"])
415                        .default_value("html"),
416                ),
417        )
418        // Legacy mode (backward compatibility)
419        .arg(
420            Arg::new("export")
421                .long("export")
422                .value_name("FORMAT")
423                .help("Export format: json, html, or both (legacy mode)")
424                .value_parser(["json", "html", "both"]),
425        )
426        .arg(
427            Arg::new("output")
428                .short('o')
429                .long("output")
430                .value_name("PATH")
431                .help("Output file path (legacy mode)")
432                .default_value("memscope_analysis"),
433        )
434        .arg(
435            Arg::new("auto-track")
436                .long("auto-track")
437                .help("Automatically track all allocations (legacy mode)")
438                .action(clap::ArgAction::SetTrue),
439        )
440        .arg(
441            Arg::new("command")
442                .help("Command to run (legacy mode)")
443                .num_args(1..),
444        )
445        .get_matches();
446
447    tracing::info!("🚀 MemScope - Memory Analysis Tool");
448
449    match matches.subcommand() {
450        Some(("run", _sub_matches)) => {
451            // Legacy run command - functionality moved to main analyze command
452            tracing::info!("Run command is deprecated. Use 'analyze' instead.");
453        }
454        Some(("analyze", sub_matches)) => {
455            if let Err(e) = handle_analyze_command(sub_matches) {
456                eprintln!("Error in analyze command: {e}");
457                std::process::exit(1);
458            }
459        }
460        _ => {
461            // Legacy mode for backward compatibility
462            if let Err(e) = handle_legacy_mode(&matches) {
463                eprintln!("Error in legacy mode: {e}");
464                std::process::exit(1);
465            }
466        }
467    }
468}
469
470fn handle_run_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
471    let command_args: Vec<&String> = matches
472        .get_many::<String>("command")
473        .ok_or("Missing command arguments")?
474        .collect();
475    let export_format = matches
476        .get_one::<String>("export")
477        .ok_or("Missing export format")?;
478    let output_path = matches
479        .get_one::<String>("output")
480        .ok_or("Missing output path")?;
481    let auto_track = matches.get_flag("auto-track");
482    let wait_completion = matches.get_flag("wait-completion");
483
484    tracing::info!(
485        "📋 Command: {}",
486        command_args
487            .iter()
488            .map(|s| s.as_str())
489            .collect::<Vec<_>>()
490            .join(" ")
491    );
492    tracing::info!("📊 Export format: {export_format}");
493    tracing::info!("📁 Output path: {output_path}");
494
495    if auto_track {
496        tracing::info!("🔍 Auto-tracking enabled");
497    }
498
499    if wait_completion {
500        tracing::info!("⏳ Wait-for-completion enabled");
501    }
502
503    // Set environment variables for the target process
504    let mut env_vars = vec![
505        ("MEMSCOPE_ENABLED", "1"),
506        ("MEMSCOPE_AUTO_EXPORT", "1"),
507        ("MEMSCOPE_EXPORT_FORMAT", export_format),
508        ("MEMSCOPE_EXPORT_PATH", output_path),
509    ];
510
511    if auto_track {
512        env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
513    }
514
515    if wait_completion {
516        env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
517    }
518
519    // Execute the target command with memory tracking
520    let result = execute_with_tracking(&command_args, &env_vars);
521
522    match result {
523        Ok(()) => {
524            tracing::info!("✅ Program execution completed successfully");
525            tracing::info!("📊 Memory analysis exported to: {output_path}");
526
527            // Post-process the exported data
528            // Post-processing would happen here if needed
529            Ok(())
530        }
531        Err(e) => {
532            tracing::error!("❌ Program execution failed: {e}");
533            Err(e)
534        }
535    }
536}
537
538fn handle_analyze_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
539    let input_path = matches
540        .get_one::<String>("input")
541        .ok_or("Missing input path")?;
542    let output_path = matches
543        .get_one::<String>("output")
544        .ok_or("Missing output path")?;
545    let format = matches
546        .get_one::<String>("format")
547        .ok_or("Missing format")?;
548
549    tracing::info!("🔍 Analyzing existing memory snapshot");
550    tracing::info!("📄 Input: {}", input_path);
551    tracing::info!("📄 Output: {}", output_path);
552    tracing::info!("📊 Format: {}", format);
553
554    // Legacy snapshot analysis - not implemented
555    let result: Result<(), Box<dyn std::error::Error>> = Ok(());
556
557    match result {
558        Ok(()) => {
559            tracing::info!("✅ Analysis completed successfully");
560            tracing::info!("📊 Report generated: {}", output_path);
561            Ok(())
562        }
563        Err(e) => {
564            tracing::error!("❌ Analysis failed: {}", e);
565            Err(e)
566        }
567    }
568}
569
570fn handle_legacy_mode(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
571    let export_format = matches.get_one::<String>("export");
572    let output_path = matches
573        .get_one::<String>("output")
574        .ok_or("Missing output path")?;
575    let auto_track = matches.get_flag("auto-track");
576
577    if let Some(command_args) = matches.get_many::<String>("command") {
578        let command_args: Vec<&String> = command_args.collect();
579
580        tracing::info!("⚠️  Using legacy mode - consider using 'memscope run' instead");
581        tracing::info!(
582            "📋 Command: {}",
583            command_args
584                .iter()
585                .map(|s| s.as_str())
586                .collect::<Vec<_>>()
587                .join(" ")
588        );
589
590        if let Some(format) = export_format {
591            tracing::info!("📊 Export format: {}", format);
592            tracing::info!("📁 Output path: {}", output_path);
593        }
594
595        if auto_track {
596            tracing::info!("🔍 Auto-tracking enabled");
597        }
598
599        // Set environment variables for the target process
600        let mut env_vars = vec![("MEMSCOPE_ENABLED", "1"), ("MEMSCOPE_AUTO_EXPORT", "1")];
601
602        if auto_track {
603            env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
604        }
605
606        if let Some(format) = export_format {
607            env_vars.push(("MEMSCOPE_EXPORT_FORMAT", format));
608            env_vars.push(("MEMSCOPE_EXPORT_PATH", output_path));
609        }
610
611        // Execute the target command with memory tracking
612        let result = execute_with_tracking(&command_args, &env_vars);
613
614        match result {
615            Ok(()) => {
616                tracing::info!("✅ Program execution completed successfully");
617
618                if export_format.is_some() {
619                    tracing::info!("📊 Memory analysis exported to: {}", output_path);
620
621                    // Post-process the exported data if needed
622                    // Post-processing would happen here if needed
623                }
624                Ok(())
625            }
626            Err(e) => {
627                tracing::error!("❌ Program execution failed: {}", e);
628                Err(e)
629            }
630        }
631    } else {
632        Err("No command provided. Use 'memscope run <command>' or 'memscope analyze <input> <output>'".into())
633    }
634}
635
636fn _analyze_existing_snapshot(
637    input_path: &str,
638    _output_path: &str,
639    format: &str,
640) -> Result<(), Box<dyn std::error::Error>> {
641    if !Path::new(input_path).exists() {
642        return Err(format!("Input file not found: {input_path}").into());
643    }
644
645    match format {
646        "html" => {
647            // Generate HTML report from JSON - not implemented
648            Err("HTML generation not implemented".into())
649        }
650        "svg" => {
651            // Generate SVG visualization from JSON - not implemented
652            Err("SVG generation not implemented".into())
653        }
654        "both" => {
655            // Both HTML and SVG generation - not implemented
656            Err("Both format generation not implemented".into())
657        }
658        _ => Err(format!("Unsupported format: {format}").into()),
659    }
660}
661
662fn generate_html_report(
663    input_path: &str,
664    output_path: &str,
665) -> Result<(), Box<dyn std::error::Error>> {
666    tracing::info!("🌐 Generating HTML report...");
667
668    // Read the JSON data
669    let json_content = std::fs::read_to_string(input_path)?;
670
671    // Create HTML content
672    let html_content = format!(
673        "<!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>",
674    );
675
676    std::fs::write(output_path, html_content)?;
677    Ok(())
678}
679
680fn generate_svg_visualization(
681    input_path: &str,
682    output_path: &str,
683) -> Result<(), Box<dyn std::error::Error>> {
684    tracing::info!("📈 Generating SVG visualization...");
685
686    // Create SVG content
687    let svg_content = format!(
688        "<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>",
689    );
690
691    std::fs::write(output_path, svg_content)?;
692    Ok(())
693}
694
695fn execute_with_tracking(
696    command_args: &[&String],
697    env_vars: &[(&str, &str)],
698) -> Result<(), Box<dyn std::error::Error>> {
699    if command_args.is_empty() {
700        return Err("No command provided".into());
701    }
702
703    let program = command_args[0];
704    let args = &command_args[1..];
705
706    tracing::info!(
707        "🔄 Executing: {} {}",
708        program,
709        args.iter()
710            .map(|s| s.as_str())
711            .collect::<Vec<_>>()
712            .join(" ")
713    );
714
715    let mut cmd = Command::new(program);
716    cmd.args(args);
717
718    // Set environment variables for memory tracking
719    for (key, value) in env_vars {
720        cmd.env(key, value);
721        tracing::info!("🔧 Setting env: {}={}", key, value);
722    }
723
724    // Inherit stdio to see the program output
725    cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
726
727    let status = cmd.status()?;
728
729    if !status.success() {
730        return Err(format!("Command failed with exit code: {:?}", status.code()).into());
731    }
732
733    // Give some time for all Drop destructors to complete
734    // This is crucial for TrackedVariable Drop implementations to finish
735    tracing::info!("⏳ Waiting for cleanup to complete...");
736    std::thread::sleep(std::time::Duration::from_millis(200));
737
738    Ok(())
739}
740
741// Update function calls to handle Results
742fn handle_run_command_wrapper(matches: &clap::ArgMatches) {
743    if let Err(e) = handle_run_command(matches) {
744        tracing::error!("❌ Run command failed: {}", e);
745        std::process::exit(1);
746    }
747}
748
749fn handle_analyze_command_wrapper(matches: &clap::ArgMatches) {
750    if let Err(e) = handle_analyze_command(matches) {
751        tracing::error!("❌ Analyze command failed: {}", e);
752        std::process::exit(1);
753    }
754}
755
756fn handle_legacy_mode_wrapper(matches: &clap::ArgMatches) {
757    if let Err(e) = handle_legacy_mode(matches) {
758        tracing::error!("❌ Legacy mode failed: {}", e);
759        std::process::exit(1);
760    }
761}
762
763fn _post_process_analysis(output_path: &str, format: &str) {
764    match format {
765        "json" => {
766            let json_path = format!("{output_path}.json");
767            if Path::new(&json_path).exists() {
768                tracing::info!("📄 JSON analysis: {}", json_path);
769                // JSON analysis would happen here
770            }
771        }
772        "html" => {
773            let html_path = format!("{output_path}.html");
774            if Path::new(&html_path).exists() {
775                tracing::info!("🌐 HTML dashboard: {}", html_path);
776            }
777        }
778        "both" => {
779            // Both JSON and HTML post-processing would happen here
780        }
781        _ => {}
782    }
783}
784
785fn analyze_json_output(json_path: &str) {
786    // Quick analysis of the exported JSON
787    if let Ok(content) = std::fs::read_to_string(json_path) {
788        if let Ok(data) = serde_json::from_str::<serde_json::Value>(&content) {
789            if let Some(stats) = data
790                .get("memory_analysis")
791                .and_then(|ma| ma.get("statistics"))
792                .and_then(|s| s.get("lifecycle_analysis"))
793            {
794                tracing::info!("📈 Quick Analysis:");
795
796                if let Some(user_stats) = stats.get("user_allocations") {
797                    if let Some(total) = user_stats.get("total_count") {
798                        tracing::info!("   👤 User allocations: {}", total);
799                    }
800                    if let Some(avg_lifetime) = user_stats.get("average_lifetime_ms") {
801                        tracing::info!("   ⏱️  Average lifetime: {}ms", avg_lifetime);
802                    }
803                }
804
805                if let Some(system_stats) = stats.get("system_allocations") {
806                    if let Some(total) = system_stats.get("total_count") {
807                        tracing::info!("   🔧 System allocations: {}", total);
808                    }
809                }
810            }
811        }
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818    use clap::{Arg, ArgMatches, Command as ClapCommand};
819    use std::fs;
820    use tempfile::TempDir;
821
822    fn create_test_matches_with_command(command: Vec<&str>) -> ArgMatches {
823        let command_strings: Vec<String> = command.iter().map(|s| s.to_string()).collect();
824        ClapCommand::new("test")
825            .arg(Arg::new("command").num_args(1..))
826            .arg(Arg::new("export").long("export"))
827            .arg(Arg::new("output").long("output"))
828            .try_get_matches_from(
829                std::iter::once("test".to_string())
830                    .chain(command_strings)
831                    .collect::<Vec<String>>(),
832            )
833            .expect("Failed to create test matches")
834    }
835
836    fn create_analyze_matches(input: &str, output: &str, format: &str) -> ArgMatches {
837        ClapCommand::new("test")
838            .arg(Arg::new("input"))
839            .arg(Arg::new("output"))
840            .arg(Arg::new("format").long("format"))
841            .try_get_matches_from(vec!["test", input, output, "--format", format])
842            .expect("Failed to create analyze matches")
843    }
844
845    #[test]
846    fn test_argument_extraction() {
847        // Test argument extraction from ArgMatches
848        let matches = create_test_matches_with_command(vec!["echo", "hello"]);
849
850        let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
851
852        assert_eq!(command_args.len(), 2);
853        assert_eq!(command_args[0], "echo");
854        assert_eq!(command_args[1], "hello");
855    }
856
857    #[test]
858    fn test_default_values() {
859        // Test default value handling
860        let matches = create_test_matches_with_command(vec!["echo", "test"]);
861
862        let export_format = matches
863            .get_one::<String>("export")
864            .map(|s| s.as_str())
865            .unwrap_or("html");
866        let output_path = matches
867            .get_one::<String>("output")
868            .map(|s| s.as_str())
869            .unwrap_or("memory_analysis");
870
871        assert_eq!(export_format, "html");
872        assert_eq!(output_path, "memory_analysis");
873    }
874
875    #[test]
876    fn test_environment_variable_setup() {
877        // Test environment variable setup logic
878        let export_format = "json";
879        let output_path = "test_output";
880        let auto_track = true;
881        let wait_completion = false;
882
883        let mut env_vars = vec![
884            ("MEMSCOPE_ENABLED", "1"),
885            ("MEMSCOPE_AUTO_EXPORT", "1"),
886            ("MEMSCOPE_EXPORT_FORMAT", export_format),
887            ("MEMSCOPE_EXPORT_PATH", output_path),
888        ];
889
890        if auto_track {
891            env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
892        }
893
894        if wait_completion {
895            env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
896        }
897
898        assert_eq!(env_vars.len(), 5); // 4 base + 1 auto_track
899        assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
900        assert!(!env_vars
901            .iter()
902            .any(|(k, _)| *k == "MEMSCOPE_WAIT_COMPLETION"));
903    }
904
905    #[test]
906    fn test_command_validation() {
907        // Test command validation logic
908        let empty_command: Vec<&String> = vec![];
909        let valid_command = ["echo".to_string(), "test".to_string()];
910        let valid_command_refs: Vec<&String> = valid_command.iter().collect();
911
912        // Test empty command
913        assert!(empty_command.is_empty());
914
915        // Test valid command
916        assert!(!valid_command_refs.is_empty());
917        assert_eq!(valid_command_refs[0], "echo");
918        assert_eq!(valid_command_refs[1], "test");
919    }
920
921    #[test]
922    fn test_generate_html_report() {
923        // Test HTML report generation
924        let temp_dir = TempDir::new().expect("Failed to create temp directory");
925        let input_path = temp_dir.path().join("test_input.json");
926        let output_path = temp_dir.path().join("test_output.html");
927
928        // Create test JSON input
929        let test_json = r#"{"memory_analysis": {"statistics": {"total_allocations": 100}}}"#;
930        fs::write(&input_path, test_json).expect("Failed to write test JSON");
931
932        // Test HTML generation
933        let result =
934            generate_html_report(input_path.to_str().unwrap(), output_path.to_str().unwrap());
935
936        assert!(result.is_ok());
937        assert!(output_path.exists());
938
939        // Verify HTML content
940        let html_content = fs::read_to_string(&output_path).expect("Failed to read HTML");
941        assert!(html_content.contains("<!DOCTYPE html>"));
942        assert!(html_content.contains("MemScope Analysis Report"));
943    }
944
945    #[test]
946    fn test_generate_svg_visualization() {
947        // Test SVG visualization generation
948        let temp_dir = TempDir::new().expect("Failed to create temp directory");
949        let input_path = temp_dir.path().join("test_input.json");
950        let output_path = temp_dir.path().join("test_output.svg");
951
952        // Create test input file
953        fs::write(&input_path, "{}").expect("Failed to write test file");
954
955        // Test SVG generation
956        let result =
957            generate_svg_visualization(input_path.to_str().unwrap(), output_path.to_str().unwrap());
958
959        assert!(result.is_ok());
960        assert!(output_path.exists());
961
962        // Verify SVG content
963        let svg_content = fs::read_to_string(&output_path).expect("Failed to read SVG");
964        assert!(svg_content.contains("<svg"));
965        assert!(svg_content.contains("MemScope Visualization"));
966    }
967
968    #[test]
969    fn test_handle_analyze_command() {
970        // Test analyze command handling
971        let temp_dir = TempDir::new().expect("Failed to create temp directory");
972        let input_path = temp_dir.path().join("input.json");
973        let output_path = temp_dir.path().join("output.html");
974
975        // Create test input file
976        fs::write(&input_path, "{}").expect("Failed to write test file");
977
978        let matches = create_analyze_matches(
979            input_path.to_str().unwrap(),
980            output_path.to_str().unwrap(),
981            "html",
982        );
983
984        let result = handle_analyze_command(&matches);
985        assert!(result.is_ok());
986    }
987
988    #[test]
989    fn test_analyze_json_output_parsing() {
990        // Test JSON output analysis logic
991        let temp_dir = TempDir::new().expect("Failed to create temp directory");
992        let json_path = temp_dir.path().join("test_analysis.json");
993
994        // Create test JSON with expected structure
995        let test_json = r#"{
996            "memory_analysis": {
997                "statistics": {
998                    "lifecycle_analysis": {
999                        "user_allocations": {
1000                            "total_count": 150,
1001                            "average_lifetime_ms": 250
1002                        },
1003                        "system_allocations": {
1004                            "total_count": 75
1005                        }
1006                    }
1007                }
1008            }
1009        }"#;
1010
1011        fs::write(&json_path, test_json).expect("Failed to write test JSON");
1012
1013        // Test JSON parsing (function doesn't return value, but should not panic)
1014        analyze_json_output(json_path.to_str().unwrap());
1015
1016        // Verify file exists and is readable
1017        assert!(json_path.exists());
1018        let content = fs::read_to_string(&json_path).expect("Failed to read JSON");
1019        let data: serde_json::Value = serde_json::from_str(&content).expect("Invalid JSON");
1020
1021        assert!(data.get("memory_analysis").is_some());
1022    }
1023
1024    #[test]
1025    fn test_format_validation() {
1026        // Test format validation logic
1027        let valid_formats = ["json", "html", "both", "svg"];
1028        let invalid_formats = ["xml", "csv", "txt"];
1029
1030        for format in valid_formats {
1031            // These formats should be handled
1032            assert!(["json", "html", "both", "svg"].contains(&format));
1033        }
1034
1035        for format in invalid_formats {
1036            // These formats should not be in valid list
1037            assert!(!["json", "html", "both", "svg"].contains(&format));
1038        }
1039    }
1040
1041    #[test]
1042    fn test_path_handling() {
1043        // Test path handling and validation
1044        let temp_dir = TempDir::new().expect("Failed to create temp directory");
1045        let valid_path = temp_dir.path().join("valid_file.json");
1046        let invalid_path = temp_dir
1047            .path()
1048            .join("nonexistent")
1049            .join("invalid_file.json");
1050
1051        // Test valid path
1052        fs::write(&valid_path, "{}").expect("Failed to write test file");
1053        assert!(valid_path.exists());
1054
1055        // Test invalid path
1056        assert!(!invalid_path.exists());
1057        assert!(!invalid_path.parent().unwrap().exists());
1058    }
1059
1060    #[test]
1061    fn test_command_args_processing() {
1062        // Test command arguments processing
1063        let test_cases = vec![
1064            (vec!["echo", "hello"], "echo hello"),
1065            (vec!["cargo", "run", "--release"], "cargo run --release"),
1066            (vec!["ls", "-la"], "ls -la"),
1067        ];
1068
1069        for (args, expected) in test_cases {
1070            let joined = args.join(" ");
1071            assert_eq!(joined, expected);
1072
1073            // Test argument splitting
1074            let program = args[0];
1075            let remaining_args = &args[1..];
1076
1077            assert_eq!(program, args[0]);
1078            assert_eq!(remaining_args.len(), args.len() - 1);
1079        }
1080    }
1081
1082    #[test]
1083    fn test_error_handling() {
1084        // Test error handling scenarios
1085
1086        // Test missing command
1087        let empty_command: Vec<&String> = vec![];
1088        assert!(empty_command.is_empty());
1089
1090        // Test missing required arguments
1091        let matches = ClapCommand::new("test")
1092            .arg(Arg::new("input"))
1093            .arg(Arg::new("output"))
1094            .try_get_matches_from(vec!["test"])
1095            .unwrap();
1096
1097        let missing_input = matches.get_one::<String>("input");
1098        let missing_output = matches.get_one::<String>("output");
1099
1100        assert!(missing_input.is_none());
1101        assert!(missing_output.is_none());
1102    }
1103
1104    #[test]
1105    fn test_legacy_mode_detection() {
1106        // Test legacy mode detection logic
1107        let matches_with_export = ClapCommand::new("test")
1108            .arg(Arg::new("export").long("export"))
1109            .arg(Arg::new("command").num_args(1..))
1110            .try_get_matches_from(vec!["test", "--export", "json", "echo", "test"])
1111            .unwrap();
1112
1113        let has_export = matches_with_export.get_one::<String>("export").is_some();
1114        let has_command = matches_with_export.get_many::<String>("command").is_some();
1115
1116        assert!(has_export);
1117        assert!(has_command);
1118
1119        // This would indicate legacy mode usage
1120        let is_legacy_mode = has_export && has_command;
1121        assert!(is_legacy_mode);
1122    }
1123
1124    #[test]
1125    fn test_run_analyze_with_valid_args() {
1126        // Test run_analyze function with valid arguments
1127        let matches = ClapCommand::new("test")
1128            .arg(Arg::new("command").num_args(1..))
1129            .arg(Arg::new("export").long("export").default_value("html"))
1130            .arg(
1131                Arg::new("output")
1132                    .long("output")
1133                    .default_value("memory_analysis"),
1134            )
1135            .try_get_matches_from(vec!["test", "echo", "hello"])
1136            .unwrap();
1137
1138        // This should not panic and should extract arguments correctly
1139        let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
1140        let export_format = matches
1141            .get_one::<String>("export")
1142            .map(|s| s.as_str())
1143            .unwrap_or("html");
1144        let output_path = matches
1145            .get_one::<String>("output")
1146            .map(|s| s.as_str())
1147            .unwrap_or("memory_analysis");
1148
1149        assert_eq!(command_args.len(), 2);
1150        assert_eq!(command_args[0], "echo");
1151        assert_eq!(command_args[1], "hello");
1152        assert_eq!(export_format, "html");
1153        assert_eq!(output_path, "memory_analysis");
1154    }
1155
1156    #[test]
1157    fn test_run_analyze_missing_command() {
1158        // Test run_analyze function with missing command
1159        let matches = ClapCommand::new("test")
1160            .arg(Arg::new("command").num_args(1..))
1161            .try_get_matches_from(vec!["test"])
1162            .unwrap();
1163
1164        let command_result = matches.get_many::<String>("command");
1165        assert!(command_result.is_none());
1166    }
1167
1168    #[test]
1169    fn test_handle_run_command_logic() {
1170        // Test the logic inside handle_run_command without actually executing
1171        let matches = ClapCommand::new("test")
1172            .arg(Arg::new("command").num_args(1..))
1173            .arg(Arg::new("export").long("export"))
1174            .arg(Arg::new("output").long("output"))
1175            .arg(
1176                Arg::new("auto-track")
1177                    .long("auto-track")
1178                    .action(clap::ArgAction::SetTrue),
1179            )
1180            .arg(
1181                Arg::new("wait-completion")
1182                    .long("wait-completion")
1183                    .action(clap::ArgAction::SetTrue),
1184            )
1185            .try_get_matches_from(vec![
1186                "test",
1187                "echo",
1188                "test",
1189                "--export",
1190                "json",
1191                "--output",
1192                "test_output",
1193                "--auto-track",
1194                "--wait-completion",
1195            ])
1196            .unwrap();
1197
1198        let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
1199        let export_format = matches.get_one::<String>("export").unwrap();
1200        let output_path = matches.get_one::<String>("output").unwrap();
1201        let auto_track = matches.get_flag("auto-track");
1202        let wait_completion = matches.get_flag("wait-completion");
1203
1204        assert_eq!(command_args.len(), 2);
1205        assert_eq!(export_format, "json");
1206        assert_eq!(output_path, "test_output");
1207        assert!(auto_track);
1208        assert!(wait_completion);
1209
1210        // Test environment variable setup
1211        let mut env_vars = vec![
1212            ("MEMSCOPE_ENABLED", "1"),
1213            ("MEMSCOPE_AUTO_EXPORT", "1"),
1214            ("MEMSCOPE_EXPORT_FORMAT", export_format),
1215            ("MEMSCOPE_EXPORT_PATH", output_path),
1216        ];
1217
1218        if auto_track {
1219            env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
1220        }
1221
1222        if wait_completion {
1223            env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
1224        }
1225
1226        assert_eq!(env_vars.len(), 6);
1227        assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
1228        assert!(env_vars.contains(&("MEMSCOPE_WAIT_COMPLETION", "1")));
1229    }
1230
1231    #[test]
1232    fn test_handle_legacy_mode_logic() {
1233        // Test handle_legacy_mode logic without executing
1234        let matches = ClapCommand::new("test")
1235            .arg(Arg::new("export").long("export"))
1236            .arg(
1237                Arg::new("output")
1238                    .long("output")
1239                    .default_value("default_output"),
1240            )
1241            .arg(
1242                Arg::new("auto-track")
1243                    .long("auto-track")
1244                    .action(clap::ArgAction::SetTrue),
1245            )
1246            .arg(Arg::new("command").num_args(1..))
1247            .try_get_matches_from(vec![
1248                "test",
1249                "--export",
1250                "html",
1251                "--output",
1252                "legacy_output",
1253                "--auto-track",
1254                "cargo",
1255                "run",
1256            ])
1257            .unwrap();
1258
1259        let export_format = matches.get_one::<String>("export");
1260        let output_path = matches.get_one::<String>("output").unwrap();
1261        let auto_track = matches.get_flag("auto-track");
1262        let command_args: Option<clap::parser::ValuesRef<String>> =
1263            matches.get_many::<String>("command");
1264
1265        assert!(export_format.is_some());
1266        assert_eq!(export_format.unwrap(), "html");
1267        assert_eq!(output_path, "legacy_output");
1268        assert!(auto_track);
1269        assert!(command_args.is_some());
1270
1271        let command_vec: Vec<&String> = command_args.unwrap().collect();
1272        assert_eq!(command_vec.len(), 2);
1273        assert_eq!(command_vec[0], "cargo");
1274        assert_eq!(command_vec[1], "run");
1275    }
1276
1277    #[test]
1278    fn test_analyze_existing_snapshot_logic() {
1279        // Test _analyze_existing_snapshot function logic
1280        let temp_dir = TempDir::new().expect("Failed to create temp directory");
1281        let existing_file = temp_dir.path().join("existing.json");
1282        let nonexistent_file = temp_dir.path().join("nonexistent.json");
1283
1284        // Create existing file
1285        fs::write(&existing_file, "{}").expect("Failed to write test file");
1286
1287        // Test with existing file
1288        assert!(existing_file.exists());
1289
1290        // Test with nonexistent file
1291        assert!(!nonexistent_file.exists());
1292
1293        // Test format handling logic
1294        let formats = ["html", "svg", "both", "invalid"];
1295        for format in formats {
1296            match format {
1297                "html" | "svg" | "both" => {
1298                    // These should be handled (though not implemented)
1299                    assert!(["html", "svg", "both"].contains(&format));
1300                }
1301                _ => {
1302                    // Invalid format
1303                    assert!(!["html", "svg", "both"].contains(&format));
1304                }
1305            }
1306        }
1307    }
1308
1309    #[test]
1310    fn test_execute_with_tracking_validation() {
1311        // Test execute_with_tracking input validation
1312        let empty_command: Vec<&String> = vec![];
1313        let valid_command = ["echo".to_string(), "hello".to_string()];
1314        let valid_refs: Vec<&String> = valid_command.iter().collect();
1315
1316        // Test empty command validation
1317        assert!(empty_command.is_empty());
1318
1319        // Test valid command structure
1320        assert!(!valid_refs.is_empty());
1321        let program = valid_refs[0];
1322        let args = &valid_refs[1..];
1323
1324        assert_eq!(program, "echo");
1325        assert_eq!(args.len(), 1);
1326        assert_eq!(args[0], "hello");
1327
1328        // Test environment variables setup
1329        let env_vars = [
1330            ("MEMSCOPE_ENABLED", "1"),
1331            ("MEMSCOPE_AUTO_EXPORT", "1"),
1332            ("TEST_VAR", "test_value"),
1333        ];
1334
1335        for (key, value) in env_vars {
1336            assert!(!key.is_empty());
1337            assert!(!value.is_empty());
1338        }
1339    }
1340
1341    #[test]
1342    fn test_html_content_generation() {
1343        // Test HTML content generation logic
1344        let input_path = "test_input.json";
1345        let expected_elements = [
1346            "<!DOCTYPE html>",
1347            "<html>",
1348            "<head>",
1349            "<title>MemScope Analysis Report</title>",
1350            "<style>",
1351            "body { font-family: Arial, sans-serif; margin: 20px; }",
1352            ".header { background: #f0f0f0; padding: 20px; border-radius: 5px; }",
1353            "</head>",
1354            "<body>",
1355            "<h1>🚀 MemScope Analysis Report</h1>",
1356        ];
1357
1358        // Simulate HTML content creation (partial)
1359        let html_start = format!(
1360            "<!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: {}", 
1361            input_path
1362        );
1363
1364        for element in expected_elements {
1365            if !element.contains("Generated from") {
1366                assert!(html_start.contains(element));
1367            }
1368        }
1369    }
1370
1371    #[test]
1372    fn test_svg_content_generation() {
1373        // Test SVG content generation logic
1374        let input_path = "test_input.json";
1375        let svg_content = format!(
1376            "<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>",
1377            input_path
1378        );
1379
1380        let expected_elements = [
1381            "<svg",
1382            "width=\"800\"",
1383            "height=\"600\"",
1384            "xmlns=\"http://www.w3.org/2000/svg\"",
1385            "<rect",
1386            "fill=\"#f0f0f0\"",
1387            "<text",
1388            "MemScope Visualization",
1389            "Generated from:",
1390            "</svg>",
1391        ];
1392
1393        for element in expected_elements {
1394            assert!(svg_content.contains(element));
1395        }
1396    }
1397
1398    #[test]
1399    fn test_command_string_joining() {
1400        // Test command string joining logic used in logging
1401        let test_cases = vec![
1402            (vec!["echo", "hello"], "echo hello"),
1403            (vec!["cargo", "run", "--release"], "cargo run --release"),
1404            (vec!["ls", "-la", "/tmp"], "ls -la /tmp"),
1405            (
1406                vec!["git", "commit", "-m", "test message"],
1407                "git commit -m test message",
1408            ),
1409        ];
1410
1411        for (args, expected) in test_cases {
1412            let joined = args.to_vec().join(" ");
1413            assert_eq!(joined, expected);
1414        }
1415    }
1416
1417    #[test]
1418    fn test_flag_combinations() {
1419        // Test various flag combinations
1420        let matches = ClapCommand::new("test")
1421            .arg(
1422                Arg::new("auto-track")
1423                    .long("auto-track")
1424                    .action(clap::ArgAction::SetTrue),
1425            )
1426            .arg(
1427                Arg::new("wait-completion")
1428                    .long("wait-completion")
1429                    .action(clap::ArgAction::SetTrue),
1430            )
1431            .arg(
1432                Arg::new("verbose")
1433                    .long("verbose")
1434                    .action(clap::ArgAction::SetTrue),
1435            )
1436            .try_get_matches_from(vec!["test", "--auto-track", "--verbose"])
1437            .unwrap();
1438
1439        let auto_track = matches.get_flag("auto-track");
1440        let wait_completion = matches.get_flag("wait-completion");
1441        let verbose = matches.get_flag("verbose");
1442
1443        assert!(auto_track);
1444        assert!(!wait_completion);
1445        assert!(verbose);
1446
1447        // Test flag-based environment variable setup
1448        let mut env_count = 0;
1449        if auto_track {
1450            env_count += 1;
1451        }
1452        if wait_completion {
1453            env_count += 1;
1454        }
1455        if verbose {
1456            env_count += 1;
1457        }
1458
1459        assert_eq!(env_count, 2); // auto_track + verbose
1460    }
1461
1462    #[test]
1463    fn test_error_message_formatting() {
1464        // Test error message formatting
1465        let test_errors = vec![
1466            ("Missing command arguments", "Missing command arguments"),
1467            ("Missing export format", "Missing export format"),
1468            ("Missing output path", "Missing output path"),
1469            (
1470                "Command failed with exit code: Some(1)",
1471                "Command failed with exit code: Some(1)",
1472            ),
1473        ];
1474
1475        for (error_msg, expected) in test_errors {
1476            assert_eq!(error_msg, expected);
1477            assert!(!error_msg.is_empty());
1478        }
1479
1480        // Test error formatting with dynamic content
1481        let exit_code = Some(1);
1482        let formatted_error = format!("Command failed with exit code: {:?}", exit_code);
1483        assert_eq!(formatted_error, "Command failed with exit code: Some(1)");
1484    }
1485
1486    #[test]
1487    fn test_file_extension_handling() {
1488        // Test file extension handling logic
1489        let test_paths = vec![
1490            ("output.html", "html"),
1491            ("output.svg", "svg"),
1492            ("output.json", "json"),
1493            ("output", ""),
1494        ];
1495
1496        for (path, expected_ext) in test_paths {
1497            let extension = path.split('.').next_back().unwrap_or("");
1498            if path.contains('.') {
1499                assert_eq!(extension, expected_ext);
1500            } else {
1501                assert_eq!(extension, path);
1502            }
1503        }
1504    }
1505
1506    #[test]
1507    fn test_timeout_duration() {
1508        // Test timeout duration logic
1509        let timeout_ms = 200;
1510        let duration = std::time::Duration::from_millis(timeout_ms);
1511
1512        assert_eq!(duration.as_millis(), 200);
1513        assert!(duration.as_millis() > 0);
1514        assert!(duration.as_millis() < 1000); // Less than 1 second
1515    }
1516}