job_queue: four more template tests read the graph
perm_change and the graceful rebuild chain walked their whole DAG to collect node kinds in order; both now assert declared_shape. The graceful one gets a sharper claim out of it -- signal and drain go between the build and the stop, and nothing else changes -- which is what distinguishes it from the non-graceful chain. reparent_bulk needed the node's payload rather than its wiring, so payload_of() reads it off the graph. The assertion is unchanged: one node carries every move, because bulk atomicity is why a single node was chosen. resubmit_while_running_is_new_dag no longer claims a node to stage the "while running" part. submit appends a container and inserts the declared group; it never consults the state of any existing node, so a running earlier DAG cannot change the outcome. The property is no dedup, covered by identical_resubmit_is_a_distinct_dag -- this one keeps the named scenario because a config bump mid-build is what people actually worry about.
This commit is contained in:
parent
ca2c479b17
commit
eab6bce813
1 changed files with 63 additions and 45 deletions
|
|
@ -239,6 +239,14 @@ fn node_of(q: &JobQueue, dag: u64, kind: &str) -> hive_jobq::NodeId {
|
|||
found.pop().expect("checked above")
|
||||
}
|
||||
|
||||
/// The payload of the one node of `kind` under `dag`, for assertions about what
|
||||
/// a node *carries* rather than how it is wired.
|
||||
fn payload_of(q: &JobQueue, dag: u64, kind: &str) -> NodeKind {
|
||||
let id = node_of(q, dag, kind);
|
||||
let sched = q.sched().lock().expect("job_queue mutex poisoned");
|
||||
sched.graph().node(id).expect("node exists").payload.clone()
|
||||
}
|
||||
|
||||
/// Kinds of every node under `dag` still `Pending` — the nodes that could yet
|
||||
/// run. Stronger than asking the scheduler what is *ready right now*: a node
|
||||
/// blocked on a dep is not ready but is very much still alive.
|
||||
|
|
@ -379,11 +387,17 @@ fn distinct_submits_never_collapse() {
|
|||
|
||||
#[test]
|
||||
fn resubmit_while_running_is_new_dag() {
|
||||
// The "while running" is not load-bearing and used to be staged by claiming
|
||||
// a node first. `submit` appends a container and inserts the declared
|
||||
// group; it never consults the state of any existing node, so whether an
|
||||
// earlier DAG is running cannot change the outcome. What is actually being
|
||||
// asserted — no dedup, ever — is `identical_resubmit_is_a_distinct_dag`.
|
||||
//
|
||||
// Kept as the *named* case because "a config bump mid-build must not be
|
||||
// swallowed" is the scenario people worry about, and a reader looking for
|
||||
// it should find it.
|
||||
let q = JobQueue::new(1);
|
||||
let a = submit(&q, rebuild("agent-a", "first"));
|
||||
let claim = claim_one(&q); // Prebuild running
|
||||
assert_eq!(claim.dag_id, a);
|
||||
// While the original runs, re-submit is legitimate new work.
|
||||
let again = submit(&q, rebuild("agent-a", "config bumped during build"));
|
||||
assert_ne!(a, again);
|
||||
assert_eq!(q.snapshot().len(), 2);
|
||||
|
|
@ -484,26 +498,25 @@ fn graceful_rebuild_chain_drains_before_stopping() {
|
|||
}),
|
||||
},
|
||||
);
|
||||
for expected in [
|
||||
"meta_sync",
|
||||
"prebuild",
|
||||
"signal",
|
||||
"drain",
|
||||
"stop_for_update",
|
||||
"swap",
|
||||
"post_swap",
|
||||
"reconcile",
|
||||
] {
|
||||
let c = claim_one(&q);
|
||||
assert_eq!(c.dag_id, id);
|
||||
assert_eq!(c.kind.as_str(), expected);
|
||||
assert!(
|
||||
q.claim_ready().is_empty(),
|
||||
"chain must serialize: nothing ready while {expected} runs"
|
||||
);
|
||||
q.complete_node(c.node_id, Ok(()));
|
||||
}
|
||||
assert_eq!(state_of(&q, id), State::Done);
|
||||
assert_eq!(
|
||||
declared_shape(&q, id)
|
||||
.iter()
|
||||
.map(|d| d.kind)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"meta_sync",
|
||||
"prebuild",
|
||||
// The graceful window goes between the build and the stop: the
|
||||
// agent gets its turn to finish before the container goes down.
|
||||
"signal",
|
||||
"drain",
|
||||
"stop_for_update",
|
||||
"swap",
|
||||
"post_swap",
|
||||
"reconcile",
|
||||
],
|
||||
"graceful inserts signal + drain ahead of the stop, and nothing else"
|
||||
);
|
||||
}
|
||||
|
||||
/// The non-graceful shape is the default everywhere except the boot sweep:
|
||||
|
|
@ -1648,21 +1661,25 @@ fn perm_change_shape_prefixes_rebuild_chain() {
|
|||
},
|
||||
),
|
||||
);
|
||||
for expected in [
|
||||
"write_perm_file",
|
||||
"meta_sync",
|
||||
"prebuild",
|
||||
"stop_for_update",
|
||||
"swap",
|
||||
"post_swap",
|
||||
"reconcile",
|
||||
] {
|
||||
let c = claim_one(&q);
|
||||
assert_eq!(c.kind.as_str(), expected);
|
||||
q.complete_node(c.node_id, Ok(()));
|
||||
}
|
||||
settle_rebuild_tail(&q, "agent-a", true);
|
||||
assert_eq!(state_of(&q, id), State::Done);
|
||||
assert_eq!(
|
||||
declared_shape(&q, id)
|
||||
.iter()
|
||||
.map(|d| d.kind)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"write_perm_file",
|
||||
"meta_sync",
|
||||
"prebuild",
|
||||
"stop_for_update",
|
||||
"swap",
|
||||
"post_swap",
|
||||
"reconcile",
|
||||
// the ok / !ok tail pair
|
||||
"emit_rebuilt",
|
||||
"emit_rebuilt",
|
||||
],
|
||||
"the perm write prefixes an otherwise ordinary rebuild chain"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1705,12 +1722,13 @@ fn reparent_bulk_shape_carries_every_move_on_one_node() {
|
|||
&q,
|
||||
templates::reparent(moves.clone(), Source::Manual, "set-parent-bulk".to_owned()),
|
||||
);
|
||||
let c = claim_one(&q);
|
||||
assert_eq!(c.kind.as_str(), "reparent");
|
||||
let NodeKind::Reparent { moves: got } = &c.kind else {
|
||||
panic!("expected a Reparent node, got {:?}", c.kind);
|
||||
assert_eq!(
|
||||
declared_shape(&q, id),
|
||||
vec![row("reparent", None, &[])],
|
||||
"one node for the whole request, not one per move"
|
||||
);
|
||||
let NodeKind::Reparent { moves: got } = payload_of(&q, id, "reparent") else {
|
||||
panic!("expected a Reparent node");
|
||||
};
|
||||
assert_eq!(got, &moves);
|
||||
q.complete_node(c.node_id, Ok(()));
|
||||
assert_eq!(state_of(&q, id), State::Done);
|
||||
assert_eq!(got, moves, "every move rides the single node");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue