use super::*;
#[test]
fn test9964_the_machine_directory_is_overridable_for_tests() {
let temp = tempfile::tempdir().expect("tempdir");
temp_env(temp.path(), || {
let dir = machine_dir().expect("a machine directory");
assert_eq!(dir, temp.path());
assert!(dir.is_dir(), "it is created rather than merely named");
let staging = staging_dir().expect("a staging directory");
assert!(staging.starts_with(&dir), "staging lives under the machine directory");
assert!(
staging.is_dir(),
"incoming transfers need somewhere to land before they are applied"
);
});
}
#[test]
fn test10002_a_rendezvous_from_another_build_reads_as_no_daemon() {
let dir = tempfile::tempdir().expect("a scratch directory");
std::fs::write(
MachineRendezvous::path_in(dir.path()),
serde_json::json!({
"version": "1.110.1396",
"pid": 4242,
"http_address": "127.0.0.1",
"http_port": 3000,
"machine_id": "y18th0mn4cq8wp01",
"machine_name": "bahram-rog",
"peer_port": 7420,
"started_at": 1787052781u64,
})
.to_string(),
)
.expect("write");
assert_eq!(
MachineRendezvous::read(dir.path()).expect("a file from another build is not an error"),
None,
"a rendezvous this build cannot read must read as no usable daemon"
);
std::fs::write(MachineRendezvous::path_in(dir.path()), "not json at all")
.expect("write");
let error = MachineRendezvous::read(dir.path())
.expect_err("a file that is not a rendezvous must be refused");
assert!(error.contains("is not a daemon rendezvous file"), "{error}");
let mine = MachineRendezvous {
version: crate::get_version().to_string(),
pid: 99,
http_address: std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
http_port: 3000,
machine_id: "k7f3q9zx2m".to_string(),
machine_name: "desktop".to_string(),
peer_address: std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
peer_port: 7420,
started_at: 1,
control_token: String::new(),
};
mine.write(dir.path()).expect("publish");
assert_eq!(MachineRendezvous::read(dir.path()).expect("read"), Some(mine));
}
#[test]
fn test9965_the_rendezvous_round_trips_and_is_private() {
let temp = tempfile::tempdir().expect("tempdir");
let dir = temp.path();
assert_eq!(MachineRendezvous::read(dir).expect("absent is not an error"), None);
let published = MachineRendezvous {
version: "1.2.3".into(),
pid: 4242,
http_address: std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
http_port: 3000,
machine_id: "k7f3q9zx2m".into(),
machine_name: "desktop".into(),
peer_address: std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
peer_port: 7420,
started_at: 1_700_000_000,
control_token: String::new(),
};
published.write(dir).expect("published");
assert_eq!(
MachineRendezvous::read(dir).expect("read").as_ref(),
Some(&published)
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(MachineRendezvous::path_in(dir))
.expect("metadata")
.permissions()
.mode();
assert_eq!(mode & 0o077, 0, "the rendezvous is readable by others: {mode:o}");
}
assert!(!MachineRendezvous::remove_if_published_by(dir, 4243).expect("asked"), "another daemon's file was removed");
assert_eq!(MachineRendezvous::read(dir).expect("read").as_ref(), Some(&published));
assert!(MachineRendezvous::remove_if_published_by(dir, 4242).expect("removed"));
assert_eq!(MachineRendezvous::read(dir).expect("read"), None);
assert!(!MachineRendezvous::remove_if_published_by(dir, 4242).expect("removing an absent one is success"));
std::fs::write(MachineRendezvous::path_in(dir), b"not a rendezvous").expect("write");
let error = MachineRendezvous::read(dir).expect_err("a foreign file must be refused");
assert!(error.contains("not a daemon rendezvous"), "{error}");
}
#[test]
fn test9966_continuity_identity_is_the_family_and_the_worktree_name() {
let temp = tempfile::tempdir().expect("tempdir");
let origin = temp.path().join("origin");
make_repo(&origin, "worktree one");
let desktop = temp.path().join("desktop/main-worktree");
let laptop = temp.path().join("mnt/code/main-worktree");
for clone in [&desktop, &laptop] {
std::fs::create_dir_all(clone.parent().expect("parent")).expect("mkdir");
clone_into(&origin, clone);
}
let fix = temp.path().join("desktop/fix-worktree");
run(&desktop, &["worktree", "add", "-q", "-b", "fix", fix.to_str().expect("path")]);
let identity = |root: &std::path::Path| {
identify_shared(&place_worktree_in(None, root).expect("placed")).expect("identified")
};
let here = identity(&desktop);
let there = identity(&laptop);
let fixing = identity(&fix);
assert_eq!(
here.shared, there.shared,
"the same worktree name of one repository at two paths is one identity"
);
assert_eq!(here.root_commit, there.root_commit);
let (here_id, fix_id) = (here.shared.expect("an id"), fixing.shared.expect("an id"));
assert_eq!(here_id.family(), fix_id.family(), "one repository, one family");
assert_ne!(here_id, fix_id, "two worktrees of it are two identities");
assert_eq!(here_id.worktree(), "main-worktree");
assert_eq!(fix_id.worktree(), "fix-worktree");
let other = temp.path().join("other");
make_repo(&other, "worktree two");
let other_id = identity(&other).shared.expect("an id");
assert_ne!(here_id.family(), other_id.family(), "two repositories, two families");
let plain = temp.path().join("plain");
std::fs::create_dir_all(&plain).expect("mkdir");
let error = identify_shared(&place_worktree_in(None, &plain).expect("placed"))
.expect_err("not a repository");
assert!(error.contains("not a git repository"), "{error}");
let unborn = temp.path().join("unborn");
std::fs::create_dir_all(&unborn).expect("mkdir");
run(&unborn, &["init", "-q", "-b", "main"]);
let nothing = identity(&unborn);
assert_eq!(nothing.root_commit, None);
assert!(nothing.shared.expect_err("no id").contains("no commits"));
let spaced = temp.path().join("my notes");
make_repo(&spaced, "spaced");
let unshareable = identity(&spaced);
assert!(unshareable.root_commit.is_some());
assert!(unshareable.shared.is_err(), "'my notes' cannot name a shared worktree");
}
#[test]
fn test20030_a_worktree_is_named_by_its_place() {
let temp = tempfile::tempdir().expect("tempdir");
let root = crate::platform::canonicalize(temp.path()).expect("canonical");
let main = root.join("alpha/main-worktree");
make_repo(&main, "alpha");
let fix = root.join("alpha/fix-worktree");
run(&main, &["worktree", "add", "-q", "-b", "fix", fix.to_str().expect("path")]);
let stray = root.join("alpha/fix-worktree/vendor/copy");
std::fs::create_dir_all(stray.parent().unwrap()).expect("mkdir");
clone_into(&main, &stray);
let prj = wsb_prj::Workshop::at(&root).expect("a PRJ");
let placed = place_worktree_in(Some(&prj), &main).expect("placed");
assert_eq!(placed.id.as_str(), "alpha:main");
assert_eq!(placed.project.as_deref(), Some("alpha"));
assert_eq!(placed.name, "main");
assert_eq!(placed.id.in_project(), Some(("alpha", "main")));
let other = place_worktree_in(Some(&prj), &fix).expect("placed");
assert_eq!(other.id.as_str(), "alpha:fix");
assert_ne!(placed.id, other.id, "two worktrees of one repository are two worktrees");
let loose = place_worktree_in(Some(&prj), &stray).expect("placed");
assert_eq!(loose.project, None, "a clone in a project folder is not one of its worktrees");
assert!(loose.id.as_str().starts_with('~'));
assert_eq!(loose.id.in_project(), None);
assert_eq!(
place_worktree_in(None, &stray).expect("placed").id,
loose.id,
"a path-named worktree is named the same with or without PRJ"
);
assert_ne!(place_worktree_in(None, &main).expect("placed").id, placed.id);
for good in ["alpha:main", "alpha:fix-2", "~0123456789abcdef"] {
assert_eq!(WorktreeId::parse(good).expect(good).as_str(), good);
}
for bad in ["alpha", ":main", "alpha:", "~0123", "~0123456789ABCDEF", "a b:main", "alpha:main:x"] {
assert!(WorktreeId::parse(bad).is_err(), "{bad:?} was taken as a worktree id");
}
}
#[test]
fn test20031_the_old_machine_directory_is_adopted_once() {
let temp = tempfile::tempdir().expect("tempdir");
let home = temp.path().join("home");
let old = home.join(".wsb");
let machine = temp.path().join("prj/.state/hosts/h0123456789ab");
let seed = |extra: &[(&str, &str)]| {
std::fs::create_dir_all(old.join("logs")).expect("mkdir");
std::fs::write(old.join("machine-identity.json"), "identity").expect("write");
std::fs::write(old.join("machine.db"), "peers").expect("write");
std::fs::write(old.join("logs/a.log"), "old log").expect("write");
std::fs::create_dir_all(old.join("disposable/logs/ops")).expect("mkdir");
std::fs::write(old.join("disposable/logs/ops/run.jsonl"), "{}").expect("write");
std::fs::write(
old.join("config.toml"),
"[github]\ntoken = \"ghp_secret\"\n\n[ui]\ntheme = \"dark\"\n",
)
.expect("write");
for (name, text) in extra {
std::fs::write(old.join(name), text).expect("write");
}
};
seed(&[("stray.txt", "mine")]);
let error = adopt_old_machine_dir(&home, &machine).expect_err("a stray file stops it");
assert!(error.contains("stray.txt"), "{error}");
assert!(old.join("machine-identity.json").exists(), "nothing moved before the refusal");
std::fs::remove_file(old.join("stray.txt")).expect("rm");
std::fs::create_dir_all(&machine).expect("mkdir");
std::fs::write(machine.join("machine.db"), "other peers").expect("write");
let error = adopt_old_machine_dir(&home, &machine).expect_err("two databases stop it");
assert!(error.contains("both exist"), "{error}");
std::fs::remove_file(machine.join("machine.db")).expect("rm");
let done = adopt_old_machine_dir(&home, &machine).expect("adopted");
assert!(!old.exists(), "the old directory is gone");
assert_eq!(std::fs::read_to_string(machine.join("machine-identity.json")).unwrap(), "identity");
assert_eq!(std::fs::read_to_string(machine.join("machine.db")).unwrap(), "peers");
assert!(!machine.join("logs").exists(), "old logs are not carried");
assert!(!machine.join("disposable").exists(), "nor are logs an older wsb filed here");
let settings = std::fs::read_to_string(machine.join("config.toml")).unwrap();
assert!(!settings.contains("ghp_secret"), "the token was carried: {settings}");
assert!(settings.contains("theme"), "the rest of the settings were lost: {settings}");
assert!(done.iter().any(|line| line.contains("without its unread [github]")), "{done:?}");
assert!(adopt_old_machine_dir(&home, &machine).expect("nothing to do").is_empty());
let dead = home.join(format!("{CLAIM_PREFIX}{}", dead_pid()));
std::fs::create_dir_all(&dead).expect("mkdir");
std::fs::write(dead.join("plugins.json"), "[]").expect("write");
adopt_old_machine_dir(&home, &machine).expect("finished");
assert!(!dead.exists());
assert_eq!(std::fs::read_to_string(machine.join("plugins.json")).unwrap(), "[]");
}
fn dead_pid() -> u32 {
(4_000_000..u32::MAX)
.find(|pid| !crate::ops::process::is_alive(*pid))
.expect("a pid nobody holds")
}
fn clone_into(origin: &std::path::Path, clone: &std::path::Path) {
let output = crate::git_backend::git_command()
.args(["clone", "-q", origin.to_str().expect("path"), clone.to_str().expect("path")])
.output()
.expect("clone");
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
}
fn make_repo(dir: &std::path::Path, content: &str) {
std::fs::create_dir_all(dir).expect("mkdir");
run(dir, &["init", "-q", "-b", "main"]);
run(dir, &["config", "user.name", "Test"]);
run(dir, &["config", "user.email", "test@example.com"]);
std::fs::write(dir.join("README.md"), format!("{content}\n")).expect("write");
run(dir, &["add", "."]);
run(dir, &["commit", "-qm", "one"]);
}
fn run(dir: &std::path::Path, args: &[&str]) {
let output = crate::git_backend::git_command()
.args(args)
.current_dir(dir)
.output()
.unwrap_or_else(|e| panic!("git {args:?}: {e}"));
assert!(
output.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn temp_env<T>(dir: &std::path::Path, body: impl FnOnce() -> T) -> T {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
let previous = std::env::var_os(MACHINE_DIR_ENV);
std::env::set_var(MACHINE_DIR_ENV, dir);
let outcome = body();
match previous {
Some(value) => std::env::set_var(MACHINE_DIR_ENV, value),
None => std::env::remove_var(MACHINE_DIR_ENV),
}
outcome
}