use std::fs;
use std::process::Command;
use std::time::{Duration, Instant};
use tempfile::TempDir;
fn sy_bin() -> String {
env!("CARGO_BIN_EXE_msy").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);
}