flash-watcher 0.1.2

A blazingly fast file watcher that executes commands when files change
Documentation
use flash_watcher::Args;
use std::sync::{Arc, Mutex};
use std::time::Duration;

// Import the stats module from main.rs
// Note: We need to test the stats functionality as used in main.rs

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_stats_collector_integration() {
        // Test stats collector as used in main.rs
        use flash_watcher::stats::StatsCollector;

        let stats_collector = Arc::new(Mutex::new(StatsCollector::new()));

        // Test basic stats operations
        {
            let mut stats = stats_collector.lock().unwrap();
            stats.record_file_change();
            stats.record_watcher_call();
            stats.update_resource_usage();
        }

        // Test concurrent access (as would happen in main.rs with threads)
        let stats_clone = Arc::clone(&stats_collector);
        let handle = std::thread::spawn(move || {
            let mut stats = stats_clone.lock().unwrap();
            stats.record_file_change();
            stats.record_watcher_call();
        });

        handle.join().unwrap();

        // Verify stats were recorded
        let stats = stats_collector.lock().unwrap();
        assert!(stats.file_changes >= 2); // At least 2 file changes recorded
        assert!(stats.watcher_calls >= 2); // At least 2 watcher calls recorded
    }

    #[test]
    fn test_stats_thread_simulation() {
        // Simulate the stats display thread from main.rs
        use flash_watcher::stats::StatsCollector;

        let stats_collector = Arc::new(Mutex::new(StatsCollector::new()));
        let _stats_interval = 1; // 1 second for testing

        // Record some activity
        {
            let mut stats = stats_collector.lock().unwrap();
            stats.record_file_change();
            stats.record_watcher_call();
            stats.update_resource_usage();
        }

        // Simulate the stats display thread (shortened for testing)
        let stats_clone = Arc::clone(&stats_collector);
        let handle = std::thread::spawn(move || {
            // Simulate one iteration of the stats thread
            std::thread::sleep(Duration::from_millis(100)); // Short sleep for testing
            let mut stats = stats_clone.lock().unwrap();
            stats.update_resource_usage();
            // In real main.rs, this would call stats.display_stats()
            // but we don't want to print in tests
        });

        handle.join().unwrap();

        // Verify the stats collector is still functional
        let stats = stats_collector.lock().unwrap();
        assert!(stats.file_changes >= 1);
        assert!(stats.watcher_calls >= 1);
    }

    #[test]
    fn test_args_stats_configuration() {
        // Test stats-related Args configuration used in main.rs
        let args_with_stats = Args {
            stats: true,
            stats_interval: 5,
            ..Args::default()
        };

        assert!(args_with_stats.stats);
        assert_eq!(args_with_stats.stats_interval, 5);

        let args_without_stats = Args {
            stats: false,
            stats_interval: 10, // default
            ..Args::default()
        };

        assert!(!args_without_stats.stats);
        assert_eq!(args_without_stats.stats_interval, 10);
    }

    #[test]
    fn test_stats_enabled_flag_logic() {
        // Test the stats_enabled flag logic from main.rs setup_watcher
        let args_with_stats = Args {
            stats: true,
            ..Args::default()
        };

        let args_without_stats = Args {
            stats: false,
            ..Args::default()
        };

        // Simulate the stats_enabled capture from main.rs
        let stats_enabled_true = args_with_stats.stats;
        let stats_enabled_false = args_without_stats.stats;

        assert!(stats_enabled_true);
        assert!(!stats_enabled_false);

        // Test conditional stats recording (as done in main.rs event handler)
        use flash_watcher::stats::StatsCollector;
        let stats_collector = Arc::new(Mutex::new(StatsCollector::new()));

        if stats_enabled_true {
            let mut stats = stats_collector.lock().unwrap();
            stats.record_watcher_call();
        }

        if stats_enabled_false {
            let mut stats = stats_collector.lock().unwrap();
            stats.record_watcher_call();
        }

        // Only one call should have been recorded (when stats was enabled)
        let stats = stats_collector.lock().unwrap();
        assert_eq!(stats.watcher_calls, 1);
    }

    #[test]
    fn test_main_args_validation_with_stats() {
        // Test Args validation with stats options
        use flash_watcher::validate_args;

        let valid_args_with_stats = Args {
            command: vec!["echo".to_string(), "test".to_string()],
            stats: true,
            stats_interval: 5,
            ..Args::default()
        };

        assert!(validate_args(&valid_args_with_stats).is_ok());

        let valid_args_without_stats = Args {
            command: vec!["echo".to_string(), "test".to_string()],
            stats: false,
            stats_interval: 10,
            ..Args::default()
        };

        assert!(validate_args(&valid_args_without_stats).is_ok());
    }

    #[test]
    fn test_initial_command_execution() {
        // Test the initial command execution logic from main.rs
        use flash_watcher::CommandRunner;

        let args_with_initial = Args {
            command: vec!["echo".to_string(), "initial".to_string()],
            initial: true,
            clear: false,
            restart: false,
            ..Args::default()
        };

        let args_without_initial = Args {
            command: vec!["echo".to_string(), "no_initial".to_string()],
            initial: false,
            clear: false,
            restart: false,
            ..Args::default()
        };

        // Test with initial command
        if args_with_initial.initial {
            let mut runner = CommandRunner::new(
                args_with_initial.command.clone(),
                args_with_initial.restart,
                args_with_initial.clear,
            );
            let result = runner.run();
            assert!(result.is_ok());
        }

        // Test without initial command (should not run)
        if args_without_initial.initial {
            // This block should not execute
            panic!("Should not execute initial command when initial=false");
        }
    }

    #[test]
    fn test_benchmark_mode_detection() {
        // Test benchmark mode detection from main.rs
        let args_with_bench = Args {
            bench: true,
            command: vec![], // Command not needed for benchmark mode
            ..Args::default()
        };

        let args_without_bench = Args {
            bench: false,
            command: vec!["echo".to_string(), "test".to_string()],
            ..Args::default()
        };

        // Test benchmark mode detection
        if args_with_bench.bench {
            // In main.rs, this would call run_benchmarks() and return early
            use flash_watcher::run_benchmarks;
            let result = run_benchmarks();
            assert!(result.is_ok() || result.is_err()); // Should not panic
        }

        // Test normal mode
        assert!(!args_without_bench.bench);
    }

    #[test]
    fn test_config_loading_integration() {
        // Test config loading integration as used in main.rs
        use flash_watcher::{load_config, merge_config};
        use std::io::Write;
        use tempfile::NamedTempFile;

        let config_yaml = r#"
command: ["cargo", "test"]
stats: true
stats_interval: 15
initial: true
"#;

        let mut file = NamedTempFile::new().unwrap();
        write!(file, "{}", config_yaml).unwrap();

        // Simulate main.rs config loading logic
        let mut args = Args {
            config: Some(file.path().to_str().unwrap().to_string()),
            ..Args::default()
        };

        if let Some(config_path) = &args.config {
            let config = load_config(config_path).unwrap();
            merge_config(&mut args, config);
        }

        // Verify config was merged correctly
        assert_eq!(args.command, vec!["cargo", "test"]);
        assert!(args.stats);
        assert_eq!(args.stats_interval, 15);
        assert!(args.initial);
    }

    #[test]
    fn test_error_handling_patterns() {
        // Test error handling patterns used in main.rs
        use flash_watcher::{load_config, validate_args};

        // Test config loading error handling
        let result = load_config("nonexistent_config.yaml");
        assert!(result.is_err());

        // Test args validation error handling
        let invalid_args = Args {
            command: vec![], // Empty command should fail validation
            ..Args::default()
        };
        let result = validate_args(&invalid_args);
        assert!(result.is_err());

        // Test that error messages are meaningful
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("command") || error_msg.contains("Command"));
    }

    #[test]
    fn test_watch_paths_processing() {
        // Test watch paths processing logic from main.rs
        let args = Args {
            watch: vec![".".to_string(), "src".to_string(), "tests".to_string()],
            ..Args::default()
        };

        // Test that all watch paths are present
        assert_eq!(args.watch.len(), 3);
        assert!(args.watch.contains(&".".to_string()));
        assert!(args.watch.contains(&"src".to_string()));
        assert!(args.watch.contains(&"tests".to_string()));

        // Test default watch path
        let default_args = Args::default();
        assert_eq!(default_args.watch, vec!["."]);
    }
}