#![allow(clippy::unwrap_used)]
use std::fs;
use std::path::{Path, PathBuf};
use yog::multiplex::dispatch;
use yog::git_env::INHERITED as INHERITED_GIT_ENV;
fn set(key: &str, value: &Path) {
unsafe { std::env::set_var(key, value) };
}
fn argv(parts: &[&str]) -> Vec<String> {
parts.iter().map(|s| (*s).to_string()).collect()
}
fn plugin_wrapper(dir: &Path, namespace: &str) -> PathBuf {
use std::os::unix::fs::PermissionsExt;
let path = dir.join(namespace);
fs::write(
&path,
format!(
"#!/bin/sh\nexec '{}' '{namespace}' \"$@\"\n",
env!("CARGO_BIN_EXE_yog")
),
)
.unwrap();
fs::set_permissions(&path, fs::Permissions::from_mode(0o755)).unwrap();
path
}
fn fixture_gitconfig(dir: &Path) -> PathBuf {
let path = dir.join("gitconfig");
fs::write(
&path,
"[user]\n\tname = Tester\n\temail = t@test.invalid\n[commit]\n\tgpgsign = false\n",
)
.unwrap();
path
}
fn git(dir: &Path, args: &[&str]) -> String {
let out = yog::git_env::git()
.current_dir(dir)
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).unwrap()
}
fn sole_child(dir: &Path) -> PathBuf {
let mut entries: Vec<_> = fs::read_dir(dir)
.unwrap()
.map(|e| e.unwrap().path())
.collect();
assert_eq!(
entries.len(),
1,
"one entry under {}: {entries:?}",
dir.display()
);
entries.remove(0)
}
fn probes_answer_and_found_nothing(world: &Path) {
for probe in ["--help", "-h", "--skill"] {
assert_eq!(dispatch(&argv(&["yog", "bl", probe])), Some(0), "{probe}");
assert!(!world.exists(), "{probe} founded {}", world.display());
}
}
fn an_unusable_anchor_fails_every_verb_but_a_probe(tmp: &Path) {
let blocked = tmp.join("not-a-dir");
fs::write(&blocked, "").unwrap();
set("XDG_DATA_HOME", &blocked.join("xdg-data"));
assert_eq!(dispatch(&argv(&["yog", "bl", "--skill", "prime"])), Some(1));
assert_eq!(dispatch(&argv(&["yog", "bl", "--help"])), Some(0));
}
#[test]
fn the_bl_arm_runs_the_whole_rung_on_the_embedded_balls() {
let tmp = tempfile::TempDir::new().unwrap();
for var in INHERITED_GIT_ENV {
unsafe { std::env::remove_var(var) };
}
for var in [
"BL_BINARY",
"LERNIE_BINARY",
"BZ_BINARY",
"YOG_NAME",
"BALLS_CLOCK",
] {
unsafe { std::env::remove_var(var) };
}
let bin = tmp.path().join("bin");
fs::create_dir(&bin).unwrap();
set("BL_DELIVERY_BINARY", &plugin_wrapper(&bin, "bl-delivery"));
set("BL_TRACKER_BINARY", &plugin_wrapper(&bin, "bl-tracker"));
set("HOME", &tmp.path().join("home"));
set("XDG_DATA_HOME", &tmp.path().join("data"));
set("XDG_STATE_HOME", &tmp.path().join("state"));
set("USER", Path::new("rung-actor"));
set("GIT_CONFIG_GLOBAL", &fixture_gitconfig(tmp.path()));
set("GIT_CONFIG_SYSTEM", Path::new("/dev/null"));
probes_answer_and_found_nothing(&tmp.path().join("data").join("yog"));
let proj = tmp.path().join("proj");
fs::create_dir(&proj).unwrap();
git(&proj, &["init", "-q", "-b", "main"]);
fs::write(proj.join("README.md"), "seed\n").unwrap();
git(&proj, &["add", "-A"]);
git(&proj, &["commit", "-qm", "seed"]);
std::env::set_current_dir(&proj).unwrap();
let proj = std::env::current_dir().unwrap();
assert_eq!(dispatch(&argv(&["yog", "bl", "--skill"])), Some(0));
assert_eq!(dispatch(&argv(&["yog", "bl"])), Some(2));
assert_eq!(dispatch(&argv(&["yog", "bl", "no-such-verb"])), Some(2));
for verb in ["prime", "sync", "install"] {
assert_eq!(
dispatch(&argv(&["yog", "bl", "--skill", verb])),
Some(0),
"{verb}"
);
}
assert_eq!(
dispatch(&argv(&["yog", "bl", "prime", "--as", "seam"])),
Some(0)
);
let tools = tmp.path().join("data/yog/world/tools");
for shim in ["bl", "lernie", "bz", "bl-delivery", "bl-tracker"] {
assert!(tools.join(shim).is_file(), "shim {shim} converged");
}
let clone = sole_child(&tmp.path().join("state/balls/clones"));
assert!(clone.join("config").is_dir(), "landing founded");
for plugin in ["bl-delivery", "bl-tracker"] {
let bound = fs::read_link(clone.join("config/config/plugins/bin").join(plugin)).unwrap();
assert_eq!(
bound,
tools.join(plugin),
"{plugin} bound to the world shim"
);
}
assert_eq!(
dispatch(&argv(&["yog", "bl", "create", "rung ball", "--as", "seam"])),
Some(0)
);
let mut balls_md: Vec<_> = fs::read_dir(clone.join("tasks/tasks"))
.unwrap()
.map(|e| e.unwrap().path())
.filter(|p| p.extension().is_some_and(|e| e == "md"))
.collect();
assert_eq!(balls_md.len(), 1, "one sealed ball: {balls_md:?}");
let task = balls_md.remove(0);
let id = task.file_stem().unwrap().to_str().unwrap().to_string();
assert_eq!(
dispatch(&argv(&["yog", "bl", "claim", &id, "--as", "seam"])),
Some(0)
);
let territory = tmp.path().join("state/balls/plugins/bl-delivery");
let worktree = territory.join(proj.strip_prefix("/").unwrap()).join(&id);
assert!(
worktree.join("README.md").is_file(),
"worktree materialized at {}",
worktree.display()
);
fs::write(worktree.join("work.txt"), "delivered\n").unwrap();
assert_eq!(
dispatch(&argv(&["yog", "bl", "close", &id, "--as", "seam"])),
Some(0)
);
let subject = git(&proj, &["log", "-1", "--format=%s", "main"]);
assert!(
subject.contains(&format!("[{id}]")),
"delivery tag on main: {subject}"
);
assert!(!task.exists(), "closed ball's file is gone");
let delivered = git(&proj, &["show", "main:work.txt"]);
assert_eq!(delivered, "delivered\n");
an_unusable_anchor_fails_every_verb_but_a_probe(tmp.path());
}