chaser 0.1.0

An automated file path synchronization tool that updates changed paths in configuration files in real time.
Documentation
// Performance and benchmark tests

use chaser::{config::Config, should_ignore_event};
use notify::{Event, EventKind, event::CreateKind};
use std::path::PathBuf;
use std::time::Instant;

fn create_test_event(paths: Vec<&str>, kind: EventKind) -> Event {
    Event {
        kind,
        paths: paths.into_iter().map(PathBuf::from).collect(),
        attrs: Default::default(),
    }
}

#[test]
fn benchmark_ignore_pattern_matching() {
    let mut ignore_patterns = Vec::new();

    for i in 0..100 {
        ignore_patterns.push(format!("*.tmp{}", i));
        ignore_patterns.push(format!("*.log{}", i));
        ignore_patterns.push(format!("cache{}/**", i));
        ignore_patterns.push(format!("build{}/**", i));
        ignore_patterns.push(format!("temp{}", i));
    }

    let test_paths = vec![
        "/project/src/main.rs",
        "/project/file.tmp50",
        "/project/cache50/data.txt",
        "/project/temp25",
    ];

    let iterations = 1000;

    for path in test_paths {
        let event = create_test_event(vec![path], EventKind::Create(CreateKind::File));

        let start = Instant::now();
        for _ in 0..iterations {
            let _ = should_ignore_event(&event, &ignore_patterns);
        }
        let duration = start.elapsed();

        let avg_time = duration.as_nanos() / iterations as u128;
        println!("Path: {}, Average time per check: {} ns", path, avg_time);

        assert!(
            avg_time < 1_000_000,
            "Pattern matching too slow for path: {}, took {} ns",
            path,
            avg_time
        );
    }
}

#[test]
fn benchmark_config_operations() {
    let mut config = Config::default();
    let iterations = 10000;

    let start = Instant::now();
    for i in 0..iterations {
        let path = format!("/test/path/{}", i);
        config.add_path(path).unwrap();
    }
    let add_duration = start.elapsed();

    println!("Added {} paths in {:?}", iterations, add_duration);
    println!("Average time per add: {:?}", add_duration / iterations);

    let start = Instant::now();
    for _ in 0..100 {
        let _ = config.validate_paths();
    }
    let validate_duration = start.elapsed();

    println!("Validated paths 100 times in {:?}", validate_duration);
    println!("Average validation time: {:?}", validate_duration / 100);

    let start = Instant::now();
    for i in 0..iterations {
        let path = format!("/test/path/{}", i);
        config.remove_path(&path).unwrap();
    }
    let remove_duration = start.elapsed();

    println!("Removed {} paths in {:?}", iterations, remove_duration);
    println!(
        "Average time per remove: {:?}",
        remove_duration / iterations
    );
}

#[test]
fn benchmark_serialization() {
    let mut config = Config::default();

    for i in 0..1000 {
        config.watch_paths.push(format!("/test/path/{}", i));
        config.ignore_patterns.push(format!("*.tmp{}", i));
    }

    let iterations = 1000;

    let start = Instant::now();
    for _ in 0..iterations {
        let _ = serde_yaml_ng::to_string(&config).unwrap();
    }
    let serialize_duration = start.elapsed();

    println!(
        "Serialized config {} times in {:?}",
        iterations, serialize_duration
    );
    println!(
        "Average serialization time: {:?}",
        serialize_duration / iterations
    );

    let yaml_content = serde_yaml_ng::to_string(&config).unwrap();

    let start = Instant::now();
    for _ in 0..iterations {
        let _: Config = serde_yaml_ng::from_str(&yaml_content).unwrap();
    }
    let deserialize_duration = start.elapsed();

    println!(
        "Deserialized config {} times in {:?}",
        iterations, deserialize_duration
    );
    println!(
        "Average deserialization time: {:?}",
        deserialize_duration / iterations
    );
}

#[test]
fn test_memory_usage() {
    let mut config = Config::default();

    for i in 0..10000 {
        config.watch_paths.push(format!(
            "/very/long/path/to/test/directory/number/{}/with/many/subdirectories/and/files",
            i
        ));
        config
            .ignore_patterns
            .push(format!("*.very_long_extension_name_{}", i));
    }

    let yaml_content = serde_yaml_ng::to_string(&config).unwrap();
    let size_kb = yaml_content.len() / 1024;
    println!("Serialized config size: {} KB", size_kb);

    let start = Instant::now();
    let _loaded_config: Config = serde_yaml_ng::from_str(&yaml_content).unwrap();
    let load_time = start.elapsed();

    println!("Loaded large config in {:?}", load_time);

    assert!(
        load_time.as_secs() < 1,
        "Config loading took too long: {:?}",
        load_time
    );
}

#[test]
fn stress_test_pattern_matching() {
    let mut ignore_patterns = Vec::new();

    for i in 0..500 {
        ignore_patterns.push(format!("*.ext{}", i));
        ignore_patterns.push(format!("dir{}/**", i));
        ignore_patterns.push(format!("*file{}", i));
        ignore_patterns.push(format!("prefix{}*", i));
    }

    let mut test_paths = Vec::new();
    for i in 0..1000 {
        test_paths.push(format!("/project/file{}.txt", i));
        test_paths.push(format!("/project/dir{}/subfile.txt", i));
        test_paths.push(format!("/project/data.ext{}", i));
        test_paths.push(format!("/project/prefix{}suffix.txt", i));
    }

    let start = Instant::now();
    let mut matched_count = 0;

    for path in &test_paths {
        let event = create_test_event(vec![path], EventKind::Create(CreateKind::File));
        if should_ignore_event(&event, &ignore_patterns) {
            matched_count += 1;
        }
    }

    let duration = start.elapsed();

    println!(
        "Processed {} paths against {} patterns in {:?}",
        test_paths.len(),
        ignore_patterns.len(),
        duration
    );
    println!("Matched {} paths", matched_count);
    println!(
        "Average time per path: {:?}",
        duration / test_paths.len() as u32
    );

    assert!(
        duration.as_secs() < 10,
        "Stress test took too long: {:?}",
        duration
    );
}

#[test]
fn test_concurrent_access_simulation() {
    use std::sync::{Arc, Mutex};
    use std::thread;

    let config = Arc::new(Mutex::new(Config::default()));
    let mut handles = vec![];

    for i in 0..10 {
        let config_clone = Arc::clone(&config);
        let handle = thread::spawn(move || {
            for j in 0..100 {
                let path = format!("/thread{}/path{}", i, j);
                {
                    let mut cfg = config_clone.lock().unwrap();
                    cfg.add_path(path.clone()).unwrap();
                }

                std::thread::sleep(std::time::Duration::from_micros(10));

                {
                    let mut cfg = config_clone.lock().unwrap();
                    cfg.remove_path(&path).unwrap();
                }
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let final_config = config.lock().unwrap();
    println!(
        "Final config has {} watch paths",
        final_config.watch_paths.len()
    );

    assert_eq!(final_config.watch_paths.len(), 1);
}

#[test]
fn benchmark_different_pattern_types() {
    let extension_patterns = (0..1000).map(|i| format!("*.ext{}", i)).collect::<Vec<_>>();
    let directory_patterns = (0..1000)
        .map(|i| format!("dir{}/**", i))
        .collect::<Vec<_>>();
    let substring_patterns = (0..1000)
        .map(|i| format!("substr{}", i))
        .collect::<Vec<_>>();

    let test_path = "/project/file.ext500";
    let event = create_test_event(vec![test_path], EventKind::Create(CreateKind::File));
    let iterations = 1000;

    let start = Instant::now();
    for _ in 0..iterations {
        let _ = should_ignore_event(&event, &extension_patterns);
    }
    let ext_duration = start.elapsed();

    let start = Instant::now();
    for _ in 0..iterations {
        let _ = should_ignore_event(&event, &directory_patterns);
    }
    let dir_duration = start.elapsed();

    let start = Instant::now();
    for _ in 0..iterations {
        let _ = should_ignore_event(&event, &substring_patterns);
    }
    let substr_duration = start.elapsed();

    println!("Extension patterns: {:?}", ext_duration);
    println!("Directory patterns: {:?}", dir_duration);
    println!("Substring patterns: {:?}", substr_duration);

    assert!(ext_duration.as_millis() < 2000);
    assert!(dir_duration.as_millis() < 2000);
    assert!(substr_duration.as_millis() < 2000);
}

#[test]
fn test_pathological_cases() {
    let long_path = "/".to_string() + &"very_long_directory_name/".repeat(100) + "file.txt";
    let patterns = vec!["*.txt".to_string()];
    let event = create_test_event(vec![&long_path], EventKind::Create(CreateKind::File));

    let start = Instant::now();
    let _ = should_ignore_event(&event, &patterns);
    let duration = start.elapsed();

    println!("Long path processing time: {:?}", duration);
    assert!(duration.as_millis() < 100, "Long path processing too slow");

    let similar_patterns = (0..1000)
        .map(|i| format!("very_similar_prefix_that_is_quite_long_{}.tmp", i))
        .collect::<Vec<_>>();

    let test_path = "/project/different_file.txt";
    let event = create_test_event(vec![test_path], EventKind::Create(CreateKind::File));

    let start = Instant::now();
    let _ = should_ignore_event(&event, &similar_patterns);
    let duration = start.elapsed();

    println!("Similar patterns processing time: {:?}", duration);
    assert!(
        duration.as_millis() < 100,
        "Similar patterns processing too slow"
    );

    let special_patterns = vec![
        "*.!@#$%^&*()".to_string(),
        "*[]{}<>?".to_string(),
        "*+=|\\".to_string(),
        "*`~".to_string(),
    ];

    let special_path = "/project/file.!@#$%^&*()";
    let event = create_test_event(vec![special_path], EventKind::Create(CreateKind::File));

    let start = Instant::now();
    let _ = should_ignore_event(&event, &special_patterns);
    let duration = start.elapsed();

    println!("Special characters processing time: {:?}", duration);
    assert!(
        duration.as_millis() < 100,
        "Special characters processing too slow"
    );
}