use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use std::process::Command; fn wrapper_path() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper") } fn real_rsync() -> Option { let candidate = "/usr/bin/rsync"; if std::path::Path::new(candidate).exists() { return Some(candidate.to_string()); } None } #[test] fn cir_rsync_wrapper_passes_through_help() { let Some(real) = real_rsync() else { return; }; let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .arg("-h") .output() .expect("run wrapper -h"); assert!( out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr) ); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); assert!(stdout.contains("rsync") || stderr.contains("rsync")); } #[test] fn cir_rsync_wrapper_rewrites_rsync_source_to_mirror_tree() { let Some(real) = real_rsync() else { return; }; let td = tempfile::tempdir().expect("tempdir"); let mirror_root = td.path().join("mirror"); let dest_root = td.path().join("dest"); let repo_root = mirror_root.join("example.net").join("repo"); std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs"); std::fs::write(repo_root.join("a.roa"), b"roa").expect("write roa"); std::fs::write(repo_root.join("nested").join("b.txt"), b"txt").expect("write txt"); std::fs::create_dir_all(&dest_root).expect("mkdir dest"); let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .env("CIR_MIRROR_ROOT", &mirror_root) .args([ "-rt", "--address", "127.0.0.1", "--contimeout=10", "--include=*/", "--include=*.roa", "--exclude=*", "rsync://example.net/repo/", dest_root.to_string_lossy().as_ref(), ]) .output() .expect("run wrapper rewrite"); assert!( out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr) ); assert_eq!( std::fs::read(dest_root.join("a.roa")).expect("read copied roa"), b"roa" ); assert!(!dest_root.join("nested").join("b.txt").exists()); } #[test] fn cir_rsync_wrapper_rewrites_module_root_without_trailing_slash_as_contents() { let Some(real) = real_rsync() else { return; }; let td = tempfile::tempdir().expect("tempdir"); let mirror_root = td.path().join("mirror"); let dest_root = td.path().join("dest"); let repo_root = mirror_root.join("example.net").join("repo"); std::fs::create_dir_all(repo_root.join("sub")).expect("mkdirs"); std::fs::write(repo_root.join("root.cer"), b"cer").expect("write cer"); std::fs::write(repo_root.join("sub").join("child.roa"), b"roa").expect("write roa"); std::fs::create_dir_all(&dest_root).expect("mkdir dest"); let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .env("CIR_MIRROR_ROOT", &mirror_root) .args([ "-rt", "--include=*/", "--include=*.cer", "--include=*.roa", "--exclude=*", "rsync://example.net/repo", dest_root.to_string_lossy().as_ref(), ]) .output() .expect("run wrapper rewrite"); assert!( out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr) ); assert_eq!( std::fs::read(dest_root.join("root.cer")).expect("read copied root cer"), b"cer" ); assert_eq!( std::fs::read(dest_root.join("sub").join("child.roa")).expect("read copied child roa"), b"roa" ); assert!( !dest_root.join("repo").exists(), "module root must not be nested under destination" ); } #[test] fn cir_rsync_wrapper_requires_mirror_root_for_rsync_source() { let Some(real) = real_rsync() else { return; }; let td = tempfile::tempdir().expect("tempdir"); let dest_root = td.path().join("dest"); std::fs::create_dir_all(&dest_root).expect("mkdir dest"); let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .args([ "-rt", "rsync://example.net/repo/", dest_root.to_string_lossy().as_ref(), ]) .output() .expect("run wrapper missing env"); assert!(!out.status.success()); assert!(String::from_utf8_lossy(&out.stderr).contains("CIR_MIRROR_ROOT")); } #[test] fn cir_rsync_wrapper_leaves_local_source_untouched() { let Some(real) = real_rsync() else { return; }; let td = tempfile::tempdir().expect("tempdir"); let src_root = td.path().join("src"); let dest_root = td.path().join("dest"); std::fs::create_dir_all(&src_root).expect("mkdir src"); std::fs::create_dir_all(&dest_root).expect("mkdir dest"); std::fs::write(src_root.join("x.cer"), b"x").expect("write source"); let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .args([ "-rt", src_root.to_string_lossy().as_ref(), dest_root.to_string_lossy().as_ref(), ]) .output() .expect("run wrapper local passthrough"); assert!( out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr) ); assert_eq!( std::fs::read(dest_root.join("src").join("x.cer")).expect("read copied file"), b"x" ); } #[test] fn cir_rsync_wrapper_local_link_mode_uses_hardlinks_for_rewritten_sources() { let Some(real) = real_rsync() else { return; }; let td = tempfile::tempdir().expect("tempdir"); let mirror_root = td.path().join("mirror"); let dest_root = td.path().join("dest"); let repo_root = mirror_root.join("example.net").join("repo"); std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs"); let src_file = repo_root.join("a.roa"); let src_nested = repo_root.join("nested").join("b.cer"); std::fs::write(&src_file, b"roa").expect("write roa"); std::fs::write(&src_nested, b"cer").expect("write cer"); std::fs::create_dir_all(&dest_root).expect("mkdir dest"); let out = Command::new(wrapper_path()) .env("REAL_RSYNC_BIN", real) .env("CIR_MIRROR_ROOT", &mirror_root) .env("CIR_LOCAL_LINK_MODE", "1") .args([ "-rt", "--delete", "--include=*/", "--include=*.roa", "--include=*.cer", "--exclude=*", "rsync://example.net/repo/", dest_root.to_string_lossy().as_ref(), ]) .output() .expect("run wrapper local-link mode"); assert!( out.status.success(), "stderr={}", String::from_utf8_lossy(&out.stderr) ); let dst_file = dest_root.join("a.roa"); let dst_nested = dest_root.join("nested").join("b.cer"); assert_eq!(std::fs::read(&dst_file).expect("read dest roa"), b"roa"); assert_eq!(std::fs::read(&dst_nested).expect("read dest cer"), b"cer"); let src_meta = std::fs::metadata(&src_file).expect("src metadata"); let dst_meta = std::fs::metadata(&dst_file).expect("dst metadata"); assert_eq!( src_meta.ino(), dst_meta.ino(), "expected hardlinked destination file" ); }