use super::*;
use crate::cli_outbound::Cli;
use crate::opslog;
use crate::test_support::{SpawnGuard, spawn_guard};
use crate::xdg::Env;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use tempfile::{TempDir, tempdir};
mod bound;
mod dispatch;
mod edit;
fn recorder(dir: &Path, name: &str, body: &str) -> (PathBuf, SpawnGuard) {
let guard = spawn_guard();
let path = dir.join(name);
fs::write(&path, body).unwrap();
let mut perms = fs::metadata(&path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&path, perms).unwrap();
(path, guard)
}
const OK_BODY: &str = "#!/bin/sh\nprintf 'OUT\\n'\nprintf 'ERR\\n' 1>&2\nexit 0\n";
struct World {
_dir: TempDir,
state: TempDir,
cli: Cli,
cwd: PathBuf,
}
impl World {
fn new(name: &str, body: &str) -> (Self, SpawnGuard) {
let dir = tempdir().unwrap();
let (bin, guard) = recorder(dir.path(), name, body);
let cwd = dir.path().to_path_buf();
(
Self {
_dir: dir,
state: tempdir().unwrap(),
cli: Cli::new(bin),
cwd,
},
guard,
)
}
fn bound(&self) -> Bound {
Bound::at(
&self.cli,
&Env::from_pairs([("XDG_DATA_HOME", self.state.path().display().to_string())]),
&self.cwd,
)
}
fn logged(&self) -> crate::opslog::OpEntry {
let mut entries = opslog::tail(self.state.path(), 8);
assert_eq!(entries.len(), 1, "exactly one op logged");
entries.pop().unwrap()
}
}
fn args_of(e: &crate::opslog::OpEntry) -> Vec<String> {
e.argv[1..].to_vec()
}
#[test]
fn message_builds_argv_and_runs_in_the_workspace() {
let (w, _g) = World::new("lernie", OK_BODY);
let ws = &w.cwd;
message(&w.bound(), w.state.path(), "TS", "a-1", "hi there").unwrap();
let e = w.logged();
assert_eq!(
args_of(&e),
vec!["message", &ws.display().to_string(), "a-1", "hi there"]
);
assert_eq!(e.cwd, ws.display().to_string());
}
#[test]
fn stop_omits_and_includes_the_children_flag() {
let (w, _g) = World::new("lernie", OK_BODY);
let ws = w.cwd.clone();
stop(&w.bound(), w.state.path(), "TS", "a-1", false).unwrap();
assert_eq!(
args_of(&w.logged()),
vec!["stop", &ws.display().to_string(), "a-1"]
);
stop(&w.bound(), w.state.path(), "TS", "a-1", true).unwrap();
let e = opslog::tail(w.state.path(), 8).pop().unwrap();
assert_eq!(
args_of(&e),
vec!["stop", &ws.display().to_string(), "a-1", "--stop-children"]
);
}
#[test]
fn scan_builds_argv() {
let (w, _g) = World::new("lernie", OK_BODY);
let ws = &w.cwd;
scan(&w.bound(), w.state.path(), "TS").unwrap();
assert_eq!(
args_of(&w.logged()),
vec!["scan", &ws.display().to_string()]
);
}
#[test]
fn retarget_builds_argv_for_one_conversation_with_no_config_flag() {
let (w, _g) = World::new("lernie", OK_BODY);
let ws = &w.cwd;
retarget(&w.bound(), w.state.path(), "TS", "a-1").unwrap();
let e = w.logged();
assert_eq!(
args_of(&e),
vec!["retarget", &ws.display().to_string(), "a-1"]
);
assert_eq!(e.cwd, ws.display().to_string());
}
#[test]
fn close_and_unclaim_build_argv_in_the_project() {
let (w, _g) = World::new("bl", OK_BODY);
let proj = &w.cwd;
close(&w.cli, w.state.path(), "TS", proj, "bl-7", "filtered").unwrap();
let e = w.logged();
assert_eq!(args_of(&e), vec!["close", "bl-7", "--as", "filtered"]);
assert_eq!(e.cwd, proj.display().to_string());
unclaim(&w.cli, w.state.path(), "TS", proj, "bl-7", "filtered").unwrap();
let e = opslog::tail(w.state.path(), 8).pop().unwrap();
assert_eq!(args_of(&e), vec!["unclaim", "bl-7", "--as", "filtered"]);
}
#[test]
fn assign_claims_the_ball_for_the_target_workspace() {
let (w, _g) = World::new("bl", OK_BODY);
let proj = &w.cwd;
assign(&w.cli, w.state.path(), "TS", proj, "bl-7", "cobalt-gecko").unwrap();
let e = w.logged();
assert_eq!(args_of(&e), vec!["claim", "bl-7", "--as", "cobalt-gecko"]);
assert_eq!(e.cwd, proj.display().to_string());
}
#[test]
fn reassign_unclaims_from_then_claims_to_both_logged() {
let (w, _g) = World::new("bl", OK_BODY);
let proj = &w.cwd;
reassign(
&w.cli,
w.state.path(),
"TS",
proj,
"bl-7",
"amber-toad",
"cobalt-gecko",
)
.unwrap();
let ops = opslog::tail(w.state.path(), 8);
assert_eq!(ops.len(), 2, "both spawns logged");
assert_eq!(
args_of(&ops[0]),
vec!["unclaim", "bl-7", "--as", "amber-toad"]
);
assert_eq!(
args_of(&ops[1]),
vec!["claim", "bl-7", "--as", "cobalt-gecko"]
);
}
#[test]
fn every_verb_stamps_the_origin_of_its_own_subject() {
let (w, guard) = World::new("lernie", OK_BODY);
message(&w.bound(), w.state.path(), "TS", "a-1", "hi").unwrap();
stop(&w.bound(), w.state.path(), "TS", "a-1", false).unwrap();
scan(&w.bound(), w.state.path(), "TS").unwrap();
for e in opslog::tail(w.state.path(), 8) {
assert_eq!(e.origin, opslog::Origin::Conversation, "{:?}", e.argv);
}
drop(guard);
let (w, _g) = World::new("bl", "#!/bin/sh\nprintf 'bl-9zzz\\n'\nexit 0\n");
let proj = w.cwd.clone();
close(&w.cli, w.state.path(), "TS", &proj, "bl-7", "amber").unwrap();
unclaim(&w.cli, w.state.path(), "TS", &proj, "bl-7", "amber").unwrap();
assign(&w.cli, w.state.path(), "TS", &proj, "bl-7", "cobalt").unwrap();
reassign(
&w.cli,
w.state.path(),
"TS",
&proj,
"bl-7",
"amber",
"cobalt",
)
.unwrap();
create(&w.cli, w.state.path(), "TS", &proj, "t", "amber", None).unwrap();
update(
&w.cli,
w.state.path(),
"TS",
&proj,
"bl-7",
"amber",
&Update::default(),
)
.unwrap();
let ops = opslog::tail(w.state.path(), 16);
assert_eq!(ops.len(), 7, "reassign logs two");
for e in ops {
assert_eq!(e.origin, opslog::Origin::Balls, "{:?}", e.argv);
}
}