use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicU32, Ordering};
static TEMP_COUNTER: AtomicU32 = AtomicU32::new(0);
fn unique_temp_root() -> PathBuf {
let id = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!("rag-rat-init-dirsel-{}-{id}", std::process::id()))
}
fn write(root: &std::path::Path, rel: &str, contents: &str) {
let path = root.join(rel);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, contents).unwrap();
}
fn init_dry_run(root: &std::path::Path) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_rag-rat"))
.args(["init", "-y", "--dry-run"])
.current_dir(root)
.output()
.expect("run rag-rat init --dry-run")
}
#[test]
fn gitignored_root_venv_does_not_drop_the_root_entrypoint() {
let root = unique_temp_root();
fs::create_dir_all(&root).unwrap();
write(&root, "manage.py", "def main():\n pass\n");
write(&root, "myapp/__init__.py", "");
write(&root, "myapp/models.py", "class A:\n pass\n");
write(&root, "env/bin/activate_this.py", "x = 1\n");
write(&root, ".gitignore", "env/\n");
let output = init_dry_run(&root);
assert!(output.status.success(), "init --dry-run should succeed: {output:?}");
let rendered = String::from_utf8_lossy(&output.stdout);
assert!(
rendered.contains("python = [\".\", \"myapp\"]"),
"expected `.` + package binding, got:\n{rendered}"
);
assert!(!root.join("rag-rat.toml").exists(), "dry-run must not write a config");
fs::remove_dir_all(&root).ok();
}
#[test]
fn non_gitignored_env_only_repo_fails_instead_of_empty_config() {
let root = unique_temp_root();
fs::create_dir_all(&root).unwrap();
write(&root, "env/bin/activate_this.py", "x = 1\n");
let output = init_dry_run(&root);
assert!(!output.status.success(), "init should fail with no indexable source: {output:?}");
fs::remove_dir_all(&root).ok();
}
#[test]
fn first_party_virtualenv_package_is_bindable() {
let root = unique_temp_root();
fs::create_dir_all(&root).unwrap();
write(&root, "src/virtualenv/__init__.py", "class Session:\n pass\n");
write(&root, "src/virtualenv/run.py", "def cli():\n pass\n");
let output = init_dry_run(&root);
assert!(output.status.success(), "init should bind the first-party package: {output:?}");
let rendered = String::from_utf8_lossy(&output.stdout);
assert!(
rendered.contains("python = [\"src\"]"),
"the virtualenv package must remain a Python binding, got:\n{rendered}"
);
fs::remove_dir_all(&root).ok();
}
#[test]
fn nested_real_venv_does_not_promote_its_ancestor() {
let root = unique_temp_root();
fs::create_dir_all(&root).unwrap();
write(&root, "myapp/__init__.py", "");
write(&root, "myapp/models.py", "class A:\n pass\n");
write(&root, "manage.py", "def main():\n pass\n");
write(&root, "tools/env/pyvenv.cfg", "home = /usr\n");
write(&root, "tools/env/bin/activate_this.py", "x = 1\n");
write(&root, "tools/env/lib/site.py", "y = 2\n");
let output = init_dry_run(&root);
assert!(output.status.success(), "init should succeed binding the real package: {output:?}");
let rendered = String::from_utf8_lossy(&output.stdout);
assert!(
rendered.contains("python = [\"myapp\"]"),
"only the real package should bind (not `.` or `tools`), got:\n{rendered}"
);
fs::remove_dir_all(&root).ok();
}