mod birth;
mod grade;
mod lanes;
mod scope;
mod tools;
use super::*;
use crate::boundary::deposit;
use crate::cli_outbound::Cli;
use crate::ui_state::SystemClock;
use serde_json::json;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use tempfile::tempdir;
fn ctx(state_root: &std::path::Path) -> ConsumerCtx {
over(
state_root,
crate::app::Snapshot::empty(0),
PathBuf::from("/data"),
Cli::new("/no/such/litany"),
)
}
fn over(
state_root: &std::path::Path,
snap: crate::app::Snapshot,
yog_data_root: PathBuf,
litany: Cli,
) -> ConsumerCtx {
over_world(
state_root,
snap,
yog_data_root,
litany,
crate::test_support::no_world(),
)
}
fn over_world(
state_root: &std::path::Path,
snap: crate::app::Snapshot,
yog_data_root: PathBuf,
litany: Cli,
world: crate::xdg::Env,
) -> ConsumerCtx {
ConsumerCtx {
yog_binary: PathBuf::from("/no/such/yog"),
world,
litany,
bl: Cli::new("/no/such/bl"),
state_root: state_root.to_path_buf(),
home: PathBuf::from("/home/x"),
yog_data_root,
balls_state_root: PathBuf::from("/balls"),
ui_path: state_root.join("ui.json"),
cell: crate::state::new_snapshot_cell(std::sync::Arc::new(snap)),
presence: crate::registry::presence::Presence::default(),
mailbox: crate::registry::mailbox::Mailbox::default(),
logins: crate::login::runs::Runs::default(),
clock: Arc::new(SystemClock),
}
}
fn world_of(root: &std::path::Path, names: &[&str]) -> crate::app::Snapshot {
let mut snap = crate::app::Snapshot::empty(0);
snap.workspaces = names
.iter()
.map(|name| {
let path = crate::binding::workspace_path(root, name);
std::fs::create_dir_all(path.join("repo.git")).expect("a workspace on disk");
crate::binding::Workspace {
path,
kind: crate::binding::WorkspaceKind::Named {
name: (*name).to_owned(),
},
}
})
.collect();
snap
}
fn fake_litany(dir: &std::path::Path) -> Cli {
let body = format!(
"#!/bin/sh\ncase \"$1\" in\n{arm}esac\nexit 0\n",
arm = crate::test_support::authoring_new_arm()
);
let path = dir.join("litany");
crate::test_support::write_exec(&path, &body);
Cli::new(path)
}
fn seed(yog_data_root: &std::path::Path) {
let litany = crate::world::layout_under(yog_data_root).litany;
std::fs::create_dir_all(&litany).unwrap();
std::fs::write(litany.join("models.yaml"), b"models: {}\n").unwrap();
}
fn listed(reply: &serde_json::Value) -> Vec<String> {
reply["rows"]
.as_array()
.map(|rows| {
rows.iter()
.filter_map(|r| r["workspace"].as_str().map(String::from))
.collect()
})
.unwrap_or_default()
}
fn client(name: &str) -> crate::registry::Client {
crate::registry::Client::parse(name).expect("a usable identity")
}
fn operator(client: crate::registry::Client) -> crate::registry::Peer {
crate::registry::Peer {
client,
grade: crate::registry::Grade::Operator,
}
}
fn seat(name: &str) -> crate::registry::Peer {
operator(client(name))
}
#[test]
fn an_empty_inbox_costs_one_listing_and_nothing_else() {
let root = tempdir().unwrap();
assert_eq!(ctx(root.path()).pass(), 0);
}
#[test]
fn a_pass_answers_from_the_latest_published_snapshot() {
let root = tempdir().unwrap();
deposit::deposit(root.path(), "q-1", &json!({"op": "workspaces"})).unwrap();
assert_eq!(ctx(root.path()).pass(), 1);
let reply = deposit::read_reply(root.path(), "q-1").unwrap();
assert_eq!(reply["ok"], true);
assert_eq!(reply["kind"], "workspaces");
assert_eq!(
reply["rows"].as_array().unwrap().len(),
0,
"the empty snapshot"
);
}
#[test]
fn one_envelope_is_answered_where_a_deposit_is() {
let root = tempdir().unwrap();
let reply = ctx(root.path()).answer(&json!({"op": "workspaces"}));
assert_eq!(reply["ok"], true);
assert_eq!(reply["kind"], "workspaces");
let refusal = ctx(root.path()).answer(&json!({"op": "enhance"}));
assert_eq!(refusal["ok"], false);
}
#[test]
fn the_thread_consumes_a_deposit_and_stops_on_drop() {
let root = tempdir().unwrap();
deposit::deposit(root.path(), "q-t", &json!({"op": "balls"})).unwrap();
let consumer = Consumer::spawn(Arc::new(ctx(root.path())));
let deadline = Instant::now() + Duration::from_secs(10);
while deposit::read_reply(root.path(), "q-t").is_none() {
assert!(Instant::now() < deadline, "the consumer never answered");
std::thread::sleep(Duration::from_millis(10));
}
drop(consumer); }
#[test]
fn the_boot_answers_a_dead_claimants_gesture_in_doubt() {
let root = tempdir().unwrap();
deposit::deposit(root.path(), "g-died", &json!({"op": "ack"})).unwrap();
drop(deposit::claim(root.path(), "g-died").unwrap()); let consumer = Consumer::spawn(Arc::new(ctx(root.path())));
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if let Some(reply) = deposit::read_reply(root.path(), "g-died") {
assert_eq!(reply["ok"], false);
assert!(reply["error"].as_str().unwrap().contains("in doubt"));
break;
}
assert!(Instant::now() < deadline, "the boot never swept the debris");
std::thread::sleep(Duration::from_millis(10));
}
drop(consumer);
}