use std::fs;
use std::process::Command;
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]
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());
}