use std::fs;
use std::process::Command;
use std::time::{Duration, Instant};
use tempfile::TempDir;
fn sy_bin() -> String {
env!("CARGO_BIN_EXE_sy").to_string()
}
fn setup_git_repo(dir: &TempDir) {
Command::new("git").args(["init"]).current_dir(dir.path()).output().unwrap();
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_100_files() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
for i in 0..100 {
fs::write(source.path().join(format!("file_{}.txt", i)), format!("content_{}", i)).unwrap();
}
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_millis(500), "Performance regression: 100 files took {:?}, expected < 500ms", elapsed);
println!("✓ 100 files synced in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_1000_files() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
for i in 0..1000 {
fs::write(source.path().join(format!("file_{}.txt", i)), format!("content_{}", i)).unwrap();
}
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_secs(3), "Performance regression: 1000 files took {:?}, expected < 3s", elapsed);
println!("✓ 1000 files synced in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_large_file() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
let content = "x".repeat(10 * 1024 * 1024);
fs::write(source.path().join("large.txt"), &content).unwrap();
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_secs(3), "Performance regression: 10MB file took {:?}, expected < 3s", elapsed);
println!("✓ 10MB file synced in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_deep_nesting() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
let mut path = source.path().to_path_buf();
for i in 0..50 {
path = path.join(format!("level_{}", i));
}
fs::create_dir_all(&path).unwrap();
fs::write(path.join("deep.txt"), "deep content").unwrap();
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_millis(500), "Performance regression: deep nesting took {:?}, expected < 500ms", elapsed);
println!("✓ 50-level deep path synced in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_idempotent_sync() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
for i in 0..100 {
fs::write(source.path().join(format!("file_{}.txt", i)), format!("content_{}", i)).unwrap();
}
Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_millis(200), "Performance regression: idempotent sync took {:?}, expected < 200ms", elapsed);
println!("✓ Idempotent sync (100 files skipped) in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_regression_gitignore_filtering() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
let mut gitignore = String::new();
for i in 0..50 {
gitignore.push_str(&format!("ignored_{}.txt\n", i));
}
fs::write(source.path().join(".gitignore"), gitignore).unwrap();
for i in 0..50 {
fs::write(source.path().join(format!("included_{}.txt", i)), format!("content_{}", i)).unwrap();
fs::write(source.path().join(format!("ignored_{}.txt", i)), format!("content_{}", i)).unwrap();
}
let start = Instant::now();
let output = Command::new(sy_bin())
.args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap(), "--gitignore"])
.output()
.unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
let synced_files = fs::read_dir(dest.path()).unwrap().filter(|e| e.as_ref().unwrap().path().is_file()).count();
assert_eq!(synced_files, 51, "Expected 51 files synced (50 included + .gitignore), got {}", synced_files);
assert!(elapsed < Duration::from_millis(500), "Performance regression: gitignore filtering took {:?}, expected < 500ms", elapsed);
println!("✓ Gitignore filtering (100 files -> 51 synced) in {:?}", elapsed);
}
#[test]
#[cfg_attr(all(windows, not(debug_assertions)), ignore)]
fn perf_memory_usage_stays_bounded() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
for i in 0..5000 {
fs::write(source.path().join(format!("file_{}.txt", i)), "x").unwrap();
}
let start = Instant::now();
let output = Command::new(sy_bin()).args([&format!("{}/", source.path().display()), dest.path().to_str().unwrap()]).output().unwrap();
let elapsed = start.elapsed();
assert!(output.status.success());
assert!(elapsed < Duration::from_secs(20), "Performance regression: 5000 files took {:?}, expected < 20s", elapsed);
println!("✓ 5000 files synced in {:?}", elapsed);
}