wip(#3001): test helpers off the container id

submit() -> insert() in tests, and the two shape walkers lose their dag
param: with no container there is no per-DAG root to filter on, nothing
to exclude (every node is real work now), and a group root genuinely has
parent = None, so the container-parent normalisation goes too. Each test
builds a fresh JobQueue, so "the DAG" is "the graph".

20 errors remain, all in tests.rs, and they are the point: changing the
helper's type from u64 to () made every site that consumed the container
id light up as `expected u64, found ()`. A type error is an exhaustive
grep -- ten helpers take a dag id, not the three I had measured.

state_of(q, dag_id) is not mechanical: it read the DAG's ROLLED-UP state,
which was the container node's own. That makes it the second consumer of
the container-as-roll-up-point, alongside hivectl's queued_dags poll.
Both want the same answer, so it waits on the same ruling.
This commit is contained in:
atlas 2026-08-04 11:53:02 +02:00 committed by mara
commit be4763678b

View file

@ -20,9 +20,15 @@ use super::*;
/// Submit a declared shape with the metadata every mechanics test uses. /// Submit a declared shape with the metadata every mechanics test uses.
/// `Source::Manual` because none of these exercise provenance — the tests that /// `Source::Manual` because none of these exercise provenance — the tests that
/// do name their own source at the call site. /// do name their own source at the call site.
fn submit(q: &JobQueue, reason: &str, declare: impl FnOnce(&JobBuilder)) -> u64 { /// Insert a declared job, naming nothing — the shape assertions read the whole
q.submit(Source::Manual, reason.to_owned(), declare) /// graph. A test that needs a handle calls `q.insert` directly and names the
.expect("valid shape") /// node it cares about.
fn insert(q: &JobQueue, declare: impl FnOnce(&JobBuilder)) {
q.insert(|b| {
declare(b);
Vec::new()
})
.expect("valid shape");
} }
fn ident(s: &str) -> hive_types::Ident { fn ident(s: &str) -> hive_types::Ident {
@ -104,25 +110,27 @@ fn when_tag(when: hive_jobq::DepWhen) -> String {
/// keeps the assertion on c0re's own product. Whether the scheduler then /// keeps the assertion on c0re's own product. Whether the scheduler then
/// *honours* those edges — runs a chain serially, holds a grant across a /// *honours* those edges — runs a chain serially, holds a grant across a
/// subtree — is `hive_jobq`'s property and is tested in `hive_jobq`. /// subtree — is `hive_jobq`'s property and is tested in `hive_jobq`.
fn declared_shape(q: &JobQueue, dag: u64) -> Vec<Declared> { fn declared_shape(q: &JobQueue) -> Vec<Declared> {
declared_shape_filtered(q, dag, &|_| true) declared_shape_filtered(q, &|_| true)
} }
fn declared_shape_filtered( /// Every node in the graph, since each test inserts into a fresh [`JobQueue`].
q: &JobQueue, ///
dag: u64, /// This used to take a DAG id and filter by `root_of(n) == Some(container)`.
keep: &dyn Fn(&NodeKind) -> bool, /// With no container node there is no per-DAG root to filter on — and nothing
) -> Vec<Declared> { /// to exclude either, because every node in the graph is now real work. Tests
/// that insert more than one job name a node per job and assert on the ids
/// [`JobQueue::insert`] hands back.
fn declared_shape_filtered(q: &JobQueue, keep: &dyn Fn(&NodeKind) -> bool) -> Vec<Declared> {
let sched = q.sched().lock().expect("job_queue mutex poisoned"); let sched = q.sched().lock().expect("job_queue mutex poisoned");
let graph = sched.graph(); let graph = sched.graph();
let root = graph.resolve_id(dag).expect("dag id is a real node id");
let kind_of = |id: NodeId| graph.node(id).map(|n| n.payload.as_str()); let kind_of = |id: NodeId| graph.node(id).map(|n| n.payload.as_str());
graph graph
.nodes() .nodes()
.filter(|n| n.id != root && graph.root_of(n.id) == Some(root) && keep(&n.payload)) .filter(|n| keep(&n.payload))
.map(|n| Declared { .map(|n| Declared {
kind: n.payload.as_str(), kind: n.payload.as_str(),
parent: n.parent.filter(|p| *p != root).and_then(kind_of), parent: n.parent.and_then(kind_of),
after: n after: n
.deps .deps
.iter() .iter()
@ -140,19 +148,18 @@ fn declared_shape_filtered(
/// Panics unless there is exactly one — every caller is about a shape where the /// Panics unless there is exactly one — every caller is about a shape where the
/// kind is unique, so two would mean the assertion had quietly stopped being /// kind is unique, so two would mean the assertion had quietly stopped being
/// about the node the test names. /// about the node the test names.
fn node_of(q: &JobQueue, dag: u64, kind: &str) -> hive_jobq::NodeId { fn node_of(q: &JobQueue, kind: &str) -> hive_jobq::NodeId {
let sched = q.sched().lock().expect("job_queue mutex poisoned"); let sched = q.sched().lock().expect("job_queue mutex poisoned");
let graph = sched.graph(); let graph = sched.graph();
let root = graph.resolve_id(dag).expect("dag id is a real node id");
let mut found: Vec<_> = graph let mut found: Vec<_> = graph
.nodes() .nodes()
.filter(|n| n.id != root && graph.root_of(n.id) == Some(root) && n.payload.as_str() == kind) .filter(|n| n.payload.as_str() == kind)
.map(|n| n.id) .map(|n| n.id)
.collect(); .collect();
assert_eq!( assert_eq!(
found.len(), found.len(),
1, 1,
"expected exactly one {kind} node in the dag" "expected exactly one {kind} node in the graph"
); );
found.pop().expect("checked above") found.pop().expect("checked above")
} }
@ -312,8 +319,8 @@ fn dag_count(q: &JobQueue) -> usize {
#[test] #[test]
fn submit_assigns_distinct_ids() { fn submit_assigns_distinct_ids() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let first = submit(&q, "first", |builder| rebuild(builder, "agent-a")); let first = insert(&q, |builder| rebuild(builder, "agent-a"));
let second = submit(&q, "second", |builder| rebuild(builder, "agent-b")); let second = insert(&q, |builder| rebuild(builder, "agent-b"));
assert_ne!(first, second); assert_ne!(first, second);
assert_eq!(dag_count(&q), 2); assert_eq!(dag_count(&q), 2);
} }
@ -326,8 +333,8 @@ fn submit_assigns_distinct_ids() {
#[test] #[test]
fn identical_resubmit_is_a_distinct_dag() { fn identical_resubmit_is_a_distinct_dag() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let first = submit(&q, "first", |builder| rebuild(builder, "agent-a")); let first = insert(&q, |builder| rebuild(builder, "agent-a"));
let resubmit = submit(&q, "again", |builder| rebuild(builder, "agent-a")); let resubmit = insert(&q, |builder| rebuild(builder, "agent-a"));
assert_ne!(first, resubmit, "no dedup: identical resubmit is a new DAG"); assert_ne!(first, resubmit, "no dedup: identical resubmit is a new DAG");
assert_eq!(dag_count(&q), 2); assert_eq!(dag_count(&q), 2);
} }
@ -335,9 +342,9 @@ fn identical_resubmit_is_a_distinct_dag() {
#[test] #[test]
fn distinct_submits_never_collapse() { fn distinct_submits_never_collapse() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let rebuild_a = submit(&q, "r", |builder| rebuild(builder, "agent-a")); let rebuild_a = insert(&q, |builder| rebuild(builder, "agent-a"));
let rebuild_b = submit(&q, "r", |builder| rebuild(builder, "agent-b")); let rebuild_b = insert(&q, |builder| rebuild(builder, "agent-b"));
let restart_a = submit(&q, "r", |builder| { let restart_a = insert(&q, |builder| {
restart_online(builder, &["agent-a"], false); restart_online(builder, &["agent-a"], false);
}); });
assert_ne!(rebuild_a, rebuild_b); assert_ne!(rebuild_a, rebuild_b);
@ -357,8 +364,8 @@ fn resubmit_while_running_is_new_dag() {
// swallowed" is the scenario people worry about, and a reader looking for // swallowed" is the scenario people worry about, and a reader looking for
// it should find it. // it should find it.
let q = JobQueue::new(1); let q = JobQueue::new(1);
let a = submit(&q, "first", |builder| rebuild(builder, "agent-a")); let a = insert(&q, |builder| rebuild(builder, "agent-a"));
let again = submit(&q, "config bumped during build", |builder| { let again = insert(&q, |builder| {
rebuild(builder, "agent-a"); rebuild(builder, "agent-a");
}); });
assert_ne!(a, again); assert_ne!(a, again);
@ -401,9 +408,9 @@ fn rebuild_chain_is_declared_serial() {
// The non-graceful shape asserted here has no quiesce chain, so nothing here // The non-graceful shape asserted here has no quiesce chain, so nothing here
// is concurrent — but the name would mislead about the graceful one. // is concurrent — but the name would mislead about the graceful one.
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "r", |builder| rebuild(builder, "agent-a")); let id = insert(&q, |builder| rebuild(builder, "agent-a"));
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
row("meta_sync", None, &[]), row("meta_sync", None, &[]),
// The brace: holds the lease + slot for everything nested below it. // The brace: holds the lease + slot for everything nested below it.
@ -481,7 +488,7 @@ fn graceful_rebuild_chain_drains_before_stopping() {
// the quiesce chain runs beside the build or nested under it, so a // the quiesce chain runs beside the build or nested under it, so a
// kind-only assertion cannot see the bug this shape exists to fix. // kind-only assertion cannot see the bug this shape exists to fix.
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
row("meta_sync", None, &[]), row("meta_sync", None, &[]),
row("agent_window", None, &[("meta_sync", "done")]), row("agent_window", None, &[("meta_sync", "done")]),
@ -530,11 +537,11 @@ fn non_graceful_rebuild_has_no_signal_or_drain() {
// job keeps its nodes to itself and inserts them, so what it built is // job keeps its nodes to itself and inserts them, so what it built is
// observable where it matters — in what the scheduler runs. // observable where it matters — in what the scheduler runs.
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "manual", |builder| { let id = insert(&q, |builder| {
templates::rebuild_nodes(builder, "agent-a", true, None); templates::rebuild_nodes(builder, "agent-a", true, None);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id) declared_shape(&q)
.iter() .iter()
.map(|d| d.kind) .map(|d| d.kind)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -567,8 +574,8 @@ fn rebuild_chain_declares_its_resources_on_the_brace() {
// `a_contended_resource_goes_to_the_oldest_waiter`. What is c0re's is // `a_contended_resource_goes_to_the_oldest_waiter`. What is c0re's is
// *which* nodes contend in the first place — a declaration, asserted here.) // *which* nodes contend in the first place — a declaration, asserted here.)
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "r", |builder| rebuild(builder, "agent-a")); let id = insert(&q, |builder| rebuild(builder, "agent-a"));
let res = |kind: &str| declared_resources(&q, node_of(&q, id, kind)); let res = |kind: &str| declared_resources(&q, node_of(&q, kind));
let agent = || Resource::Agent("agent-a".to_owned()); let agent = || Resource::Agent("agent-a".to_owned());
assert_eq!( assert_eq!(
@ -612,7 +619,7 @@ fn rebuild_chain_declares_its_resources_on_the_brace() {
#[test] #[test]
fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() { fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() {
let q = JobQueue::new(4); let q = JobQueue::new(4);
let id = submit(&q, "hive-wide", |builder| { let id = insert(&q, |builder| {
restart_online(builder, &["agent-a", "agent-b"], false); restart_online(builder, &["agent-a", "agent-b"], false);
}); });
// A hive-wide restart is ONE DAG, not one-per-agent. // A hive-wide restart is ONE DAG, not one-per-agent.
@ -623,7 +630,7 @@ fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() {
// Those two declared facts are what "they run concurrently" *means* here — // Those two declared facts are what "they run concurrently" *means* here —
// that a scheduler then does run independent, resource-disjoint roots at // that a scheduler then does run independent, resource-disjoint roots at
// once is hive_jobq's property, tested there. // once is hive_jobq's property, tested there.
let heads: Vec<_> = declared_shape(&q, id) let heads: Vec<_> = declared_shape(&q)
.into_iter() .into_iter()
.filter(|d| d.kind == "stop_for_update") .filter(|d| d.kind == "stop_for_update")
.collect(); .collect();
@ -648,7 +655,7 @@ fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() {
#[test] #[test]
fn multi_agent_stop_is_one_dag_with_concurrent_per_agent_subgraphs() { fn multi_agent_stop_is_one_dag_with_concurrent_per_agent_subgraphs() {
let q = JobQueue::new(4); let q = JobQueue::new(4);
let id = submit(&q, "hive-wide stop", |builder| { let id = insert(&q, |builder| {
stop_online(builder, &["agent-a", "agent-b"], false); stop_online(builder, &["agent-a", "agent-b"], false);
}); });
// A hive-wide stop is ONE DAG, not one-per-agent. // A hive-wide stop is ONE DAG, not one-per-agent.
@ -657,7 +664,7 @@ fn multi_agent_stop_is_one_dag_with_concurrent_per_agent_subgraphs() {
// is a group root with no node-deps, holding only its own agent's lease. // is a group root with no node-deps, holding only its own agent's lease.
// Independent roots on disjoint resources is what "concurrently" means at // Independent roots on disjoint resources is what "concurrently" means at
// this layer — the running of them is hive_jobq's. // this layer — the running of them is hive_jobq's.
let heads: Vec<_> = declared_shape(&q, id) let heads: Vec<_> = declared_shape(&q)
.into_iter() .into_iter()
.filter(|d| d.kind == "set_wanted") .filter(|d| d.kind == "set_wanted")
.collect(); .collect();
@ -681,7 +688,7 @@ fn multi_agent_start_one_dag_folds_per_agent_stale_rebuild() {
let q = JobQueue::new(4); let q = JobQueue::new(4);
// fresh: offline + not stale → SetWanted → Reconcile. // fresh: offline + not stale → SetWanted → Reconcile.
// stale: offline + stale → SetWanted → «rebuild subgraph». // stale: offline + stale → SetWanted → «rebuild subgraph».
let id = submit(&q, "hive-wide start", |builder| { let id = insert(&q, |builder| {
power::start_nodes( power::start_nodes(
builder, builder,
&[ &[
@ -741,13 +748,13 @@ fn offline_agents_skip_mechanical_nodes_but_keep_reconcile() {
// read and node exec. // read and node exec.
let q = JobQueue::new(4); let q = JobQueue::new(4);
// Offline graceful stop → SetWanted(Off) → Reconcile (no Signal/Drain). // Offline graceful stop → SetWanted(Off) → Reconcile (no Signal/Drain).
let stop = submit(&q, "stop down", |builder| { let stop = insert(&q, |builder| {
power::stop_nodes(builder, &[("down".to_owned(), false)], true); power::stop_nodes(builder, &[("down".to_owned(), false)], true);
}); });
// Offline restart → a lone Reconcile (no SetWanted, no StopForUpdate): // Offline restart → a lone Reconcile (no SetWanted, no StopForUpdate):
// nothing to bounce, and restart never rewrites intent, so the tail // nothing to bounce, and restart never rewrites intent, so the tail
// Reconcile converges the down agent to its existing `wanted`. // Reconcile converges the down agent to its existing `wanted`.
let restart = submit(&q, "restart down", |builder| { let restart = insert(&q, |builder| {
power::restart_nodes(builder, &[("down2".to_owned(), false)], true); power::restart_nodes(builder, &[("down2".to_owned(), false)], true);
}); });
let shape = |id: u64| -> Vec<String> { let shape = |id: u64| -> Vec<String> {
@ -797,7 +804,7 @@ fn boot_sweep_nodes_declare_their_own_resources() {
) )
.expect("valid shape"); .expect("valid shape");
let mut lock = declared_resources(&q, node_of(&q, id, "meta_lock")); let mut lock = declared_resources(&q, node_of(&q, "meta_lock"));
lock.sort_by_key(|r| format!("{r:?}")); lock.sort_by_key(|r| format!("{r:?}"));
assert_eq!( assert_eq!(
lock, lock,
@ -806,7 +813,7 @@ fn boot_sweep_nodes_declare_their_own_resources() {
); );
assert_eq!( assert_eq!(
declared_resources(&q, node_of(&q, id, "reconcile")), declared_resources(&q, node_of(&q, "reconcile")),
vec![Resource::Agent("drifted-agent".to_owned())], vec![Resource::Agent("drifted-agent".to_owned())],
"a boot Reconcile touches the container, so it holds that agent's lease" "a boot Reconcile touches the container, so it holds that agent's lease"
); );
@ -901,8 +908,8 @@ fn rebuild_reconcile_waits_for_the_whole_build_subtree() {
// **did** flatten this chain, and the guarantee survives only because the // **did** flatten this chain, and the guarantee survives only because the
// roll-up point moved with it. // roll-up point moved with it.
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "r", |builder| rebuild(builder, "agent-a")); let id = insert(&q, |builder| rebuild(builder, "agent-a"));
let shape = declared_shape(&q, id); let shape = declared_shape(&q);
let parent_of = |kind: &str| { let parent_of = |kind: &str| {
shape shape
.iter() .iter()
@ -973,7 +980,7 @@ fn rebuild_reconcile_waits_for_the_whole_build_subtree() {
#[test] #[test]
fn a_fanned_out_mechanical_node_declares_its_agent_lease() { fn a_fanned_out_mechanical_node_declares_its_agent_lease() {
let q = JobQueue::new(4); let q = JobQueue::new(4);
let id = submit(&q, "fan-out", |builder| { let id = insert(&q, |builder| {
templates::fanned_out_mechanical( templates::fanned_out_mechanical(
builder, builder,
NodeKind::Start { NodeKind::Start {
@ -981,9 +988,9 @@ fn a_fanned_out_mechanical_node_declares_its_agent_lease() {
}, },
); );
}); });
assert_eq!(declared_shape(&q, id), vec![row("start", None, &[])]); assert_eq!(declared_shape(&q), vec![row("start", None, &[])]);
assert_eq!( assert_eq!(
declared_resources(&q, node_of(&q, id, "start")), declared_resources(&q, node_of(&q, "start")),
vec![Resource::Agent("agent-a".to_owned())], vec![Resource::Agent("agent-a".to_owned())],
"the fanned-out node carries the lease itself, rather than relying on \ "the fanned-out node carries the lease itself, rather than relying on \
whoever happened to fan it out" whoever happened to fan it out"
@ -1020,7 +1027,7 @@ fn a_meta_lock_grows_one_rebuild_subgraph_per_agent() {
// One chain per agent, each an independent group root — so the two rebuild // One chain per agent, each an independent group root — so the two rebuild
// concurrently, each on its own lease. // concurrently, each on its own lease.
let shape = declared_shape(&q, id); let shape = declared_shape(&q);
let heads: Vec<_> = shape let heads: Vec<_> = shape
.iter() .iter()
.filter(|d| d.kind == "meta_sync") .filter(|d| d.kind == "meta_sync")
@ -1046,7 +1053,7 @@ fn a_meta_lock_grows_one_rebuild_subgraph_per_agent() {
#[test] #[test]
fn cancel_clears_queued_dag() { fn cancel_clears_queued_dag() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "r", |builder| rebuild(builder, "agent-a")); let id = insert(&q, |builder| rebuild(builder, "agent-a"));
assert!(q.cancel(id), "fully-queued dag cancels"); assert!(q.cancel(id), "fully-queued dag cancels");
// The operator sees `Cancelled` the moment the cancel returns — the spared // The operator sees `Cancelled` the moment the cancel returns — the spared
// tail is still `Pending`, and a DAG must not read `Queued` back to the // tail is still `Pending`, and a DAG must not read `Queued` back to the
@ -1075,7 +1082,7 @@ fn cancel_clears_queued_dag() {
#[test] #[test]
fn cancel_drops_one_agents_branch_leaving_the_rest() { fn cancel_drops_one_agents_branch_leaving_the_rest() {
let q = JobQueue::new(2); let q = JobQueue::new(2);
let id = submit(&q, "r", |builder| { let id = insert(&q, |builder| {
restart_online(builder, &["agent-a", "agent-b"], false); restart_online(builder, &["agent-a", "agent-b"], false);
}); });
// Per-agent subgraphs hang directly off the container, one per agent. // Per-agent subgraphs hang directly off the container, one per agent.
@ -1152,19 +1159,19 @@ fn cancelled_power_op_runs_no_compensating_node() {
let case = format!("graceful={graceful} running={running}"); let case = format!("graceful={graceful} running={running}");
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "bounce", |builder| { let id = insert(&q, |builder| {
power::restart_nodes(builder, &targets, graceful); power::restart_nodes(builder, &targets, graceful);
}); });
assert_cancels_clean(&q, id, false, &format!("restart {case}")); assert_cancels_clean(&q, id, false, &format!("restart {case}"));
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "stop", |builder| { let id = insert(&q, |builder| {
power::stop_nodes(builder, &targets, graceful); power::stop_nodes(builder, &targets, graceful);
}); });
assert_cancels_clean(&q, id, true, &format!("stop {case}")); assert_cancels_clean(&q, id, true, &format!("stop {case}"));
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "start", |builder| { let id = insert(&q, |builder| {
power::start_nodes(builder, &[("agent-a".to_owned(), running, false)]); power::start_nodes(builder, &[("agent-a".to_owned(), running, false)]);
}); });
assert_cancels_clean(&q, id, true, &format!("start {case}")); assert_cancels_clean(&q, id, true, &format!("start {case}"));
@ -1185,7 +1192,7 @@ fn cancelled_power_op_runs_no_compensating_node() {
#[test] #[test]
fn cancelled_dag_still_runs_its_approval_tail() { fn cancelled_dag_still_runs_its_approval_tail() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "approval #7", |builder| { let id = insert(&q, |builder| {
templates::approval_deploy(builder, "agent-a", 7); templates::approval_deploy(builder, "agent-a", 7);
}); });
assert!(q.cancel(id), "fully-queued dag cancels"); assert!(q.cancel(id), "fully-queued dag cancels");
@ -1215,7 +1222,7 @@ fn cancelled_dag_still_runs_its_approval_tail() {
assert_eq!(state_of(&q, id), State::Finishing); assert_eq!(state_of(&q, id), State::Finishing);
// An unrelated group landing in the same graph doesn't disturb this one's // An unrelated group landing in the same graph doesn't disturb this one's
// state — a root rolls up its own subtree, not the graph. // state — a root rolls up its own subtree, not the graph.
let _other = submit(&q, "r", |builder| rebuild(builder, "agent-b")); let _other = insert(&q, |builder| rebuild(builder, "agent-b"));
assert_eq!(state_of(&q, id), State::Finishing); assert_eq!(state_of(&q, id), State::Finishing);
} }
@ -1229,12 +1236,12 @@ fn cancelled_dag_still_runs_its_approval_tail() {
#[test] #[test]
fn deploy_dag_runs_phases_in_order_and_tails_a_failed_apply() { fn deploy_dag_runs_phases_in_order_and_tails_a_failed_apply() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "approval #7", |builder| { let id = insert(&q, |builder| {
templates::approval_deploy(builder, "agent-a", 7); templates::approval_deploy(builder, "agent-a", 7);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
// The window is the group root and holds the meta window for the // The window is the group root and holds the meta window for the
// whole subtree; the three phases are its sub-nodes. // whole subtree; the three phases are its sub-nodes.
@ -1287,12 +1294,12 @@ fn deploy_apply_grows_rebuild_subgraph_and_finalizes_after_it() {
// children run (`a_completing_node_grows_the_work_it_declared`, // children run (`a_completing_node_grows_the_work_it_declared`,
// `parent_parks_in_finishing_until_children_roll_up`). // `parent_parks_in_finishing_until_children_roll_up`).
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "deploy graft", |builder| { let id = insert(&q, |builder| {
templates::deploy_rebuild_nodes(builder, "agent-a", 11); templates::deploy_rebuild_nodes(builder, "agent-a", 11);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
row("meta_sync", None, &[]), row("meta_sync", None, &[]),
row("agent_window", None, &[("meta_sync", "done")]), row("agent_window", None, &[("meta_sync", "done")]),
@ -1467,11 +1474,11 @@ fn error_truncation_cuts_on_a_char_boundary() {
#[test] #[test]
fn graceful_stop_shape_signal_drain_reconcile() { fn graceful_stop_shape_signal_drain_reconcile() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "graceful", |builder| { let id = insert(&q, |builder| {
stop_online(builder, &["agent-a"], true); stop_online(builder, &["agent-a"], true);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
// The whole stop hangs under `set_wanted`: the durable intent is // The whole stop hangs under `set_wanted`: the durable intent is
// written first, and the mechanical steps are its sub-nodes. // written first, and the mechanical steps are its sub-nodes.
@ -1494,8 +1501,8 @@ fn graceful_stop_shape_signal_drain_reconcile() {
// `exec.rs`. // `exec.rs`.
assert_eq!( assert_eq!(
[ [
declared_resources(&q, node_of(&q, id, "signal")), declared_resources(&q, node_of(&q, "signal")),
declared_resources(&q, node_of(&q, id, "drain")), declared_resources(&q, node_of(&q, "drain")),
], ],
[vec![], vec![]], [vec![], vec![]],
"the quiesce pair borrows the brace's lease and declares nothing" "the quiesce pair borrows the brace's lease and declares nothing"
@ -1505,11 +1512,11 @@ fn graceful_stop_shape_signal_drain_reconcile() {
#[test] #[test]
fn spawn_shape_provision_create_dropin_reconcile() { fn spawn_shape_provision_create_dropin_reconcile() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "approval #7 spawn", |builder| { let id = insert(&q, |builder| {
templates::spawn(builder, "newbie", 7); templates::spawn(builder, "newbie", 7);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![ vec![
row("provision", None, &[]), row("provision", None, &[]),
row("create", Some("provision"), &[]), row("create", Some("provision"), &[]),
@ -1530,7 +1537,7 @@ fn spawn_shape_provision_create_dropin_reconcile() {
#[test] #[test]
fn perm_change_shape_prefixes_rebuild_chain() { fn perm_change_shape_prefixes_rebuild_chain() {
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "perm", |builder| { let id = insert(&q, |builder| {
templates::perm_change( templates::perm_change(
builder, builder,
"agent-a", "agent-a",
@ -1541,7 +1548,7 @@ fn perm_change_shape_prefixes_rebuild_chain() {
); );
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id) declared_shape(&q)
.iter() .iter()
.map(|d| d.kind) .map(|d| d.kind)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -1569,15 +1576,15 @@ fn reparent_shape_is_a_lone_agentless_meta_window_node() {
// `MetaLock`, and it must declare the meta window — a topology commit // `MetaLock`, and it must declare the meta window — a topology commit
// must not land inside another node's staged deploy window. // must not land inside another node's staged deploy window.
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "set-parent", |builder| { let id = insert(&q, |builder| {
templates::reparent(builder, vec![(ident("alice"), Some(ident("bob")))]); templates::reparent(builder, vec![(ident("alice"), Some(ident("bob")))]);
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![row("reparent", None, &[])], vec![row("reparent", None, &[])],
"one node, no rebuild subgraph" "one node, no rebuild subgraph"
); );
let node = node_of(&q, id, "reparent"); let node = node_of(&q, "reparent");
assert_eq!( assert_eq!(
declared_resources(&q, node), declared_resources(&q, node),
vec![Resource::MetaWindow], vec![Resource::MetaWindow],
@ -1593,11 +1600,11 @@ fn reparent_bulk_shape_carries_every_move_on_one_node() {
// request is the reason a single node was chosen in the first place. // request is the reason a single node was chosen in the first place.
let moves = vec![(ident("alice"), Some(ident("bob"))), (ident("carol"), None)]; let moves = vec![(ident("alice"), Some(ident("bob"))), (ident("carol"), None)];
let q = JobQueue::new(1); let q = JobQueue::new(1);
let id = submit(&q, "set-parent-bulk", |builder| { let id = insert(&q, |builder| {
templates::reparent(builder, moves.clone()); templates::reparent(builder, moves.clone());
}); });
assert_eq!( assert_eq!(
declared_shape(&q, id), declared_shape(&q),
vec![row("reparent", None, &[])], vec![row("reparent", None, &[])],
"one node for the whole request, not one per move" "one node for the whole request, not one per move"
); );