use std::fs;
use std::process::Command;
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]
fn test_exclude_flag_basic() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("file.txt"), "content").unwrap();
fs::write(source.path().join("debug.log"), "log").unwrap();
fs::write(source.path().join("error.log"), "error").unwrap();
let output = Command::new(sy_bin())
.args(["--exclude", "*.log", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success(), "sy --exclude failed: {}", String::from_utf8_lossy(&output.stderr));
assert!(dest.path().join("file.txt").exists());
assert!(!dest.path().join("debug.log").exists(), "*.log should be excluded");
assert!(!dest.path().join("error.log").exists(), "*.log should be excluded");
}
#[test]
fn test_exclude_flag_multiple() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("file.txt"), "content").unwrap();
fs::write(source.path().join("debug.log"), "log").unwrap();
fs::write(source.path().join("cache.tmp"), "tmp").unwrap();
let output = Command::new(sy_bin())
.args(["--exclude", "*.log", "--exclude", "*.tmp", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
assert!(dest.path().join("file.txt").exists());
assert!(!dest.path().join("debug.log").exists());
assert!(!dest.path().join("cache.tmp").exists());
}
#[test]
fn test_exclude_directory() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("file.txt"), "content").unwrap();
fs::create_dir(source.path().join("node_modules")).unwrap();
fs::write(source.path().join("node_modules/package.json"), "{}").unwrap();
let output = Command::new(sy_bin())
.args(["--exclude", "node_modules/", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
assert!(dest.path().join("file.txt").exists());
assert!(!dest.path().join("node_modules").exists(), "node_modules/ should be excluded");
}
#[test]
fn test_include_flag_basic() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("main.rs"), "fn main() {}").unwrap();
fs::write(source.path().join("lib.rs"), "pub fn lib() {}").unwrap();
fs::write(source.path().join("readme.md"), "# Readme").unwrap();
let output = Command::new(sy_bin())
.args(["--include", "*.rs", "--exclude", "*", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success(), "sy --include failed: {}", String::from_utf8_lossy(&output.stderr));
assert!(dest.path().join("main.rs").exists(), "*.rs should be included");
assert!(dest.path().join("lib.rs").exists(), "*.rs should be included");
assert!(!dest.path().join("readme.md").exists(), "*.md should be excluded");
}
#[test]
fn test_include_exclude_order_matters() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("important.log"), "important").unwrap();
fs::write(source.path().join("debug.log"), "debug").unwrap();
fs::write(source.path().join("file.txt"), "content").unwrap();
let output = Command::new(sy_bin())
.args([
"--include",
"important.log",
"--exclude",
"*.log",
&format!("{}/", source.path().display()),
dest.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
assert!(dest.path().join("important.log").exists(), "important.log should be included (first match wins)");
assert!(!dest.path().join("debug.log").exists(), "debug.log should be excluded");
assert!(dest.path().join("file.txt").exists());
}
#[test]
fn test_filter_flag_include_syntax() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("main.rs"), "fn main() {}").unwrap();
fs::write(source.path().join("test.py"), "print('test')").unwrap();
let output = Command::new(sy_bin())
.args(["--filter", "+ *.rs", "--filter", "- *", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success(), "sy --filter failed: {}", String::from_utf8_lossy(&output.stderr));
assert!(dest.path().join("main.rs").exists());
assert!(!dest.path().join("test.py").exists());
}
#[test]
fn test_filter_flag_exclude_syntax() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::write(source.path().join("file.txt"), "content").unwrap();
fs::write(source.path().join("debug.log"), "log").unwrap();
let output = Command::new(sy_bin())
.args(["--filter", "- *.log", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
assert!(dest.path().join("file.txt").exists());
assert!(!dest.path().join("debug.log").exists());
}
#[test]
fn test_exclude_from_file() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
let exclude_file = TempDir::new().unwrap();
setup_git_repo(&source);
let patterns_path = exclude_file.path().join("excludes.txt");
fs::write(&patterns_path, "*.log\n*.tmp\n# comment\nbuild/\n").unwrap();
fs::write(source.path().join("file.txt"), "content").unwrap();
fs::write(source.path().join("debug.log"), "log").unwrap();
fs::write(source.path().join("cache.tmp"), "tmp").unwrap();
fs::create_dir(source.path().join("build")).unwrap();
fs::write(source.path().join("build/out.txt"), "out").unwrap();
let output = Command::new(sy_bin())
.args([
"--exclude-from",
patterns_path.to_str().unwrap(),
&format!("{}/", source.path().display()),
dest.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success(), "sy --exclude-from failed: {}", String::from_utf8_lossy(&output.stderr));
assert!(dest.path().join("file.txt").exists());
assert!(!dest.path().join("debug.log").exists());
assert!(!dest.path().join("cache.tmp").exists());
assert!(!dest.path().join("build").exists());
}
#[test]
fn test_include_from_file() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
let filter_file = TempDir::new().unwrap();
setup_git_repo(&source);
let patterns_path = filter_file.path().join("filters.txt");
fs::write(&patterns_path, "+ *.rs\n+ *.toml\n- *\n").unwrap();
fs::write(source.path().join("main.rs"), "fn main() {}").unwrap();
fs::write(source.path().join("Cargo.toml"), "[package]").unwrap();
fs::write(source.path().join("readme.md"), "# Readme").unwrap();
let output = Command::new(sy_bin())
.args([
"--filter",
"+ *.rs",
"--filter",
"+ *.toml",
"--filter",
"- *",
&format!("{}/", source.path().display()),
dest.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success(), "sy --filter failed: {}", String::from_utf8_lossy(&output.stderr));
assert!(dest.path().join("main.rs").exists());
assert!(dest.path().join("Cargo.toml").exists());
assert!(!dest.path().join("readme.md").exists());
}
#[test]
fn test_nested_directory_exclusion() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::create_dir_all(source.path().join("src/components")).unwrap();
fs::write(source.path().join("src/main.rs"), "fn main() {}").unwrap();
fs::write(source.path().join("src/components/button.rs"), "pub struct Button;").unwrap();
fs::create_dir_all(source.path().join("target/debug")).unwrap();
fs::write(source.path().join("target/debug/binary"), "binary").unwrap();
let output = Command::new(sy_bin())
.args(["--exclude", "target/", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
assert!(dest.path().join("src/main.rs").exists());
assert!(dest.path().join("src/components/button.rs").exists());
assert!(!dest.path().join("target").exists(), "target/ should be excluded");
}
#[test]
fn test_basename_vs_path_matching() {
let source = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
setup_git_repo(&source);
fs::create_dir_all(source.path().join("dir1")).unwrap();
fs::create_dir_all(source.path().join("dir2")).unwrap();
fs::write(source.path().join("test.log"), "root log").unwrap();
fs::write(source.path().join("dir1/test.log"), "dir1 log").unwrap();
fs::write(source.path().join("dir2/other.log"), "dir2 log").unwrap();
let output = Command::new(sy_bin())
.args(["--exclude", "test.log", &format!("{}/", source.path().display()), dest.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
assert!(!dest.path().join("test.log").exists(), "test.log in root should be excluded");
assert!(!dest.path().join("dir1/test.log").exists(), "test.log in subdir should be excluded");
assert!(dest.path().join("dir2/other.log").exists());
}