c0re's queue tests no longer drive the scheduler
The last two claim-driven tests were both arranging node states to observe something that never needed a run: `settled_dag_leaves_the_snapshot_despite_its_skipped_branch` completed all seven nodes of a rebuild to assert the DAG left the snapshot. That is one predicate over a list of states. `shown_on_wire` is it, split out of `dag_view`, and the cases can now be named rather than arranged — including the empty set, the one input where "any" and "all" disagree. It takes states rather than projected nodes so the caller skips projecting what it is about to discard; a `NodeView` costs a `build_logs` lookup. `failed_node_cancels_downstream_but_afterany_reconcile_runs` asserted three unrelated things from one arranged failure: the cascade (hive_jobq's, and already tested there), the wire filter (now `shown_on_wire`), and the roll-up. `DagView::rollup_state` lives in hive-host-sock, which had no tests at all — it does now, next to the invariant, covering the ordering its own doc comment says has silently disagreed with the frontend before. With nothing left claiming, `Claimed` / `ClaimReady` / `CompleteNode` / `claim_one` / `settle_rebuild_tail` are deleted. Claim/complete sites in `job_queue/tests.rs`: 109 -> 0. jobq narrows to match: `settle` is gone (it was a `claim_one` loop returning a Vec, and its only callers were tests — it lives in the test module now), `claim_one` is private, and `complete_growing` is `pub(crate)`. `claim_next` is the whole run-loop surface. `complete` stays `pub` for one caller, noted at the definition: `submit` completes a group root with no logic of its own so it parks in `Finishing` and its children unblock. That is a statement about the node, not an event to report, and it wants to be expressible at insert time.
This commit is contained in:
parent
ab5744a2bd
commit
e646656c92
4 changed files with 334 additions and 266 deletions
|
|
@ -1,13 +1,14 @@
|
|||
//! The settle loop — drives a [`Graph`] to completion over a resource pool the
|
||||
//! scheduler owns directly.
|
||||
//!
|
||||
//! [`Scheduler::settle`] claims every currently-runnable pending node (its
|
||||
//! [`Scheduler::claim_next`] claims one currently-runnable pending node (its
|
||||
//! [`Dep::Node`] edges satisfied *and* all its [`Dep::Resource`] units acquired
|
||||
//! atomically), marks it `Running`, records the units it holds, and returns the
|
||||
//! newly-started ids for the caller's runner to execute. The runner reports each
|
||||
//! node's result back with [`Scheduler::complete`]; a running node may grow more
|
||||
//! work first via [`Scheduler::append`]. Concurrency is emergent from resource
|
||||
//! capacity — there is no separate active-node cap.
|
||||
//! atomically), marks it `Running`, records the units it holds, and hands back
|
||||
//! a future that executes the node **and completes it**, so "forgot to finish
|
||||
//! the node" is not expressible. One at a time is the primitive on purpose: it
|
||||
//! lets the caller choose between claiming again and backing off, which a batch
|
||||
//! return can't express. A running node may grow more work by declaring into
|
||||
//! the builder it was handed. Concurrency is emergent from resource capacity.
|
||||
//!
|
||||
//! Single-threaded by design: the scheduler is the only driver, holds the
|
||||
//! [`ResourceTable`] as a plain owned field, mutating it through `&mut self` —
|
||||
|
|
@ -88,8 +89,8 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
}
|
||||
|
||||
/// Append a node under `parent` — e.g. a running node growing more work into
|
||||
/// its own subtree. Delegates to [`Graph::insert`]; call [`Scheduler::settle`]
|
||||
/// afterwards to start it once it is runnable.
|
||||
/// its own subtree. Delegates to [`Graph::insert`]; claim again afterwards
|
||||
/// to start it once it is runnable.
|
||||
///
|
||||
/// # Errors
|
||||
/// Propagates [`GraphError`] for a dangling dependency or parent id.
|
||||
|
|
@ -116,8 +117,7 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
/// [`Graph::insert_unchecked`]: [`crate::builder::check_job_shape`] has
|
||||
/// already decided every rejection the graph could raise, so re-validating
|
||||
/// per node could only report a problem *after* the earlier nodes were
|
||||
/// inserted. Call [`Scheduler::settle`] afterwards to start whatever became
|
||||
/// runnable.
|
||||
/// inserted. Claim again afterwards to start whatever became runnable.
|
||||
///
|
||||
/// **Atomic in the job's own shape.** A forward edge, a forward parent, or
|
||||
/// a request for a handle this job never declared is rejected *before* the
|
||||
|
|
@ -146,11 +146,11 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
/// returned for the caller to execute. `None` means nothing is runnable
|
||||
/// right now — which is a different statement from "nothing is pending".
|
||||
///
|
||||
/// One-at-a-time is the primitive on purpose: it lets the caller decide
|
||||
/// between claiming again immediately and backing off, a choice a batch
|
||||
/// return can't express. [`Self::settle`] is this in a loop.
|
||||
/// **Private**: [`Self::claim_next`] is the only way out of this crate.
|
||||
/// Claiming without the future that completes the node is the sequence the
|
||||
/// seam exists to make inexpressible, so the primitive stays in here.
|
||||
#[must_use]
|
||||
pub fn claim_one(&mut self) -> Option<NodeId> {
|
||||
fn claim_one(&mut self) -> Option<NodeId> {
|
||||
let pending: Vec<NodeId> = self
|
||||
.graph
|
||||
.nodes()
|
||||
|
|
@ -216,24 +216,6 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Claim every currently-runnable pending node. Equivalent to calling
|
||||
/// [`Self::claim_one`] until it yields `None`: a node started by an earlier
|
||||
/// iteration is `Running`, not terminal, so it cannot satisfy another
|
||||
/// node's dependency here — it only consumes resources.
|
||||
///
|
||||
/// ⚠️ Each iteration rescans the pending set, so this is O(n²) in the
|
||||
/// number of nodes claimed where the old single-pass version was O(n). The
|
||||
/// graph is bounded by history retention, so that is affordable; it is
|
||||
/// stated rather than left to be discovered.
|
||||
#[must_use]
|
||||
pub fn settle(&mut self) -> Vec<NodeId> {
|
||||
let mut started = Vec::new();
|
||||
while let Some(id) = self.claim_one() {
|
||||
started.push(id);
|
||||
}
|
||||
started
|
||||
}
|
||||
|
||||
/// Try to start node `id`. For each resource it needs, decide per the parent
|
||||
/// tree (see the module docs): acquire fresh units (owner), acquire an extra
|
||||
/// unit (grant lent elsewhere), or borrow an ancestor's grant. The fresh set
|
||||
|
|
@ -321,8 +303,16 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
/// (every child `Done`) or [`State::Failed`] (any child `Failed`/`Cancelled`).
|
||||
/// On failure it is `Failed` at once and its pending sub-nodes are cancelled
|
||||
/// (gated on a `Finishing` the parent never reached). Terminality then
|
||||
/// propagates up the parent chain. Call [`Scheduler::settle`] again afterwards
|
||||
/// to start newly-unblocked work.
|
||||
/// propagates up the parent chain. Claim again afterwards to start
|
||||
/// newly-unblocked work.
|
||||
///
|
||||
/// ⚠️ **Still `pub` for one caller**, and that caller is the last hole in
|
||||
/// this wall: a host inserting a group root with no logic of its own
|
||||
/// completes it immediately so it parks in `Finishing` and its children
|
||||
/// become runnable. That is a statement about the *node* ("this one has no
|
||||
/// work"), not an event to report, and it wants to be expressible at
|
||||
/// insert time so completion can go `pub(crate)` alongside
|
||||
/// [`Self::complete_growing`].
|
||||
pub fn complete(&mut self, id: NodeId, outcome: Outcome) {
|
||||
match outcome {
|
||||
Outcome::Failed(error) => {
|
||||
|
|
@ -361,7 +351,7 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
/// would misreport that, and leaving it `Running` forever would wedge the
|
||||
/// DAG. So the error is returned for the caller to log, not used to abort
|
||||
/// the completion. This crate has no logger of its own; the caller does.
|
||||
pub fn complete_growing(
|
||||
pub(crate) fn complete_growing(
|
||||
&mut self,
|
||||
id: NodeId,
|
||||
outcome: Outcome,
|
||||
|
|
@ -622,7 +612,7 @@ impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
|||
/// done" signal) supplies parent→child ordering; `Dep::Node` edges (which the
|
||||
/// graph restricts to the same parent group) supply sibling ordering.
|
||||
/// `Dep::Resource` edges are handled by the atomic acquire in
|
||||
/// [`Scheduler::settle`], not here.
|
||||
/// [`Scheduler::try_start`], not here.
|
||||
fn node_deps_satisfied(&self, id: NodeId) -> bool {
|
||||
let Some(node) = self.graph.node(id) else {
|
||||
return false;
|
||||
|
|
@ -664,6 +654,25 @@ mod tests {
|
|||
name.to_owned()
|
||||
}
|
||||
|
||||
/// Claim every currently-runnable node, as arrangement for the assertions
|
||||
/// below. Equivalent to calling [`Scheduler::claim_one`] until it yields
|
||||
/// `None`: a node started by an earlier iteration is `Running`, not
|
||||
/// terminal, so it cannot satisfy another node's dependency here — it only
|
||||
/// consumes resources.
|
||||
///
|
||||
/// **Was `Scheduler::settle`, a public method.** It was a `claim_one` loop
|
||||
/// returning a `Vec`, and production never wanted the batch: the run loop
|
||||
/// takes one node at a time through [`Scheduler::claim_next`] so it can
|
||||
/// choose between claiming again and backing off, which a batch return
|
||||
/// can't express. The only callers were tests, so it lives with them.
|
||||
fn settle<N, R: Clone + Eq + Hash>(s: &mut Scheduler<N, R>) -> Vec<NodeId> {
|
||||
let mut started = Vec::new();
|
||||
while let Some(id) = s.claim_one() {
|
||||
started.push(id);
|
||||
}
|
||||
started
|
||||
}
|
||||
|
||||
/// A graph + a resource table with `build-slot` set to `slots`.
|
||||
fn scheduler_with_slots(slots: u32) -> Scheduler<&'static str, String> {
|
||||
let mut table = ResourceTable::new();
|
||||
|
|
@ -706,7 +715,7 @@ mod tests {
|
|||
fn a_completing_node_grows_the_work_it_declared() {
|
||||
let mut s = scheduler_with_slots(1);
|
||||
let n = s.append("emitter", vec![], None).expect("insert");
|
||||
assert_eq!(s.settle(), vec![n]);
|
||||
assert_eq!(settle(&mut s), vec![n]);
|
||||
|
||||
let grown = JobBuilder::new();
|
||||
grown.node("child-a");
|
||||
|
|
@ -732,7 +741,7 @@ mod tests {
|
|||
fn a_failed_node_grows_nothing() {
|
||||
let mut s = scheduler_with_slots(1);
|
||||
let n = s.append("emitter", vec![], None).expect("insert");
|
||||
assert_eq!(s.settle(), vec![n]);
|
||||
assert_eq!(settle(&mut s), vec![n]);
|
||||
|
||||
let grown = JobBuilder::new();
|
||||
grown.node("never-runs");
|
||||
|
|
@ -766,12 +775,12 @@ mod tests {
|
|||
let b = s.append("b", res_dep("build-slot"), None).expect("b");
|
||||
let c = s.append("c", res_dep("build-slot"), None).expect("c");
|
||||
|
||||
assert_eq!(s.settle(), vec![a], "cap 1: only the first can start");
|
||||
assert_eq!(settle(&mut s), vec![a], "cap 1: only the first can start");
|
||||
s.complete(a, Outcome::Done);
|
||||
// b and c are both satisfiable now; b was inserted first.
|
||||
assert_eq!(s.settle(), vec![b], "the freed unit goes to b, not c");
|
||||
assert_eq!(settle(&mut s), vec![b], "the freed unit goes to b, not c");
|
||||
s.complete(b, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![c]);
|
||||
assert_eq!(settle(&mut s), vec![c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -780,7 +789,7 @@ mod tests {
|
|||
let n = s
|
||||
.append("build", res_dep("build-slot"), None)
|
||||
.expect("insert");
|
||||
assert_eq!(s.settle(), vec![n]);
|
||||
assert_eq!(settle(&mut s), vec![n]);
|
||||
assert_eq!(s.graph().node(n).unwrap().state, State::Running);
|
||||
assert_eq!(avail(&s, "build-slot"), 0);
|
||||
// No children → completing it goes straight to Done (skips Finishing).
|
||||
|
|
@ -800,7 +809,7 @@ mod tests {
|
|||
assert!(s.graph().node(ok).unwrap().started_at.is_none());
|
||||
assert!(s.graph().node(ok).unwrap().finished_at.is_none());
|
||||
|
||||
let started = s.settle();
|
||||
let started = settle(&mut s);
|
||||
assert!(started.contains(&ok) && started.contains(&bad));
|
||||
// Running → started_at stamped, finished_at still none.
|
||||
assert!(s.graph().node(ok).unwrap().started_at.is_some());
|
||||
|
|
@ -836,11 +845,11 @@ mod tests {
|
|||
let b = s.append("b", res_dep("build-slot"), None).expect("b");
|
||||
let c = s.append("c", res_dep("build-slot"), None).expect("c");
|
||||
// cap 2 → a + b start, c blocks on the exhausted slot.
|
||||
assert_eq!(s.settle(), vec![a, b]);
|
||||
assert_eq!(settle(&mut s), vec![a, b]);
|
||||
assert_eq!(s.graph().node(c).unwrap().state, State::Pending);
|
||||
// a finishes → its slot frees → c can now start.
|
||||
s.complete(a, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![c]);
|
||||
assert_eq!(settle(&mut s), vec![c]);
|
||||
assert_eq!(s.graph().node(c).unwrap().state, State::Running);
|
||||
}
|
||||
|
||||
|
|
@ -852,16 +861,16 @@ mod tests {
|
|||
let root = s.append("root", vec![], None).expect("root");
|
||||
let c1 = s.append("c1", vec![], Some(root)).expect("c1");
|
||||
let c2 = s.append("c2", vec![], Some(root)).expect("c2");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
// Children can't start yet — parent still Running (logic not done).
|
||||
assert!(s.settle().is_empty(), "children gated on parent logic");
|
||||
assert!(settle(&mut s).is_empty(), "children gated on parent logic");
|
||||
s.complete(root, Outcome::Done);
|
||||
assert_eq!(
|
||||
s.graph().node(root).unwrap().state,
|
||||
State::Finishing,
|
||||
"logic done, children pending → Finishing"
|
||||
);
|
||||
let mut started = s.settle();
|
||||
let mut started = settle(&mut s);
|
||||
started.sort();
|
||||
let mut expected = vec![c1, c2];
|
||||
expected.sort();
|
||||
|
|
@ -885,9 +894,9 @@ mod tests {
|
|||
let mut s: Scheduler<&str, String> = Scheduler::new(Graph::new(), ResourceTable::new());
|
||||
let root = s.append("root", vec![], None).expect("root");
|
||||
let child = s.append("child", vec![], Some(root)).expect("child");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![child]);
|
||||
assert_eq!(settle(&mut s), vec![child]);
|
||||
s.complete(child, Outcome::Failed(String::new()));
|
||||
assert_eq!(
|
||||
s.graph().node(root).unwrap().state,
|
||||
|
|
@ -905,10 +914,10 @@ mod tests {
|
|||
let r = s.append("R", res_dep("build-slot"), None).expect("R");
|
||||
let c1 = s.append("c1", res_dep("build-slot"), Some(r)).expect("c1");
|
||||
let c2 = s.append("c2", vec![after_ok(c1)], Some(r)).expect("c2");
|
||||
assert_eq!(s.settle(), vec![r]);
|
||||
assert_eq!(settle(&mut s), vec![r]);
|
||||
s.complete(r, Outcome::Done); // → Finishing (children pending)
|
||||
assert_eq!(avail(&s, "build-slot"), 0, "held: subtree not terminal");
|
||||
assert_eq!(s.settle(), vec![c1], "c1 borrows R's slot");
|
||||
assert_eq!(settle(&mut s), vec![c1], "c1 borrows R's slot");
|
||||
assert_eq!(avail(&s, "build-slot"), 0, "borrow reuses R's unit");
|
||||
s.complete(c1, Outcome::Done);
|
||||
assert_eq!(
|
||||
|
|
@ -916,7 +925,7 @@ mod tests {
|
|||
0,
|
||||
"still held: c2 pending in subtree"
|
||||
);
|
||||
assert_eq!(s.settle(), vec![c2]);
|
||||
assert_eq!(settle(&mut s), vec![c2]);
|
||||
s.complete(c2, Outcome::Done);
|
||||
assert_eq!(
|
||||
avail(&s, "build-slot"),
|
||||
|
|
@ -934,14 +943,14 @@ mod tests {
|
|||
let owner = s
|
||||
.append("owner", res_dep("agent/foo"), None)
|
||||
.expect("owner");
|
||||
assert_eq!(s.settle(), vec![owner]);
|
||||
assert_eq!(settle(&mut s), vec![owner]);
|
||||
assert_eq!(avail(&s, "agent/foo"), 0);
|
||||
let child = s
|
||||
.append("child", res_dep("agent/foo"), Some(owner))
|
||||
.expect("child");
|
||||
s.complete(owner, Outcome::Done); // → Finishing
|
||||
assert_eq!(avail(&s, "agent/foo"), 0, "held while a borrower pends");
|
||||
assert_eq!(s.settle(), vec![child]);
|
||||
assert_eq!(settle(&mut s), vec![child]);
|
||||
assert_eq!(avail(&s, "agent/foo"), 0, "borrow reuses the one unit");
|
||||
s.complete(child, Outcome::Done);
|
||||
assert_eq!(avail(&s, "agent/foo"), 1);
|
||||
|
|
@ -964,13 +973,13 @@ mod tests {
|
|||
let great = s
|
||||
.append("great", res_dep("agent/foo"), Some(grand))
|
||||
.expect("great");
|
||||
assert_eq!(s.settle(), vec![r]);
|
||||
assert_eq!(settle(&mut s), vec![r]);
|
||||
s.complete(r, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![child], "child borrows R's grant");
|
||||
assert_eq!(settle(&mut s), vec![child], "child borrows R's grant");
|
||||
s.complete(child, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![grand], "grand covered, no deadlock");
|
||||
assert_eq!(settle(&mut s), vec![grand], "grand covered, no deadlock");
|
||||
s.complete(grand, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![great], "great covered too");
|
||||
assert_eq!(settle(&mut s), vec![great], "great covered too");
|
||||
assert_eq!(avail(&s, "agent/foo"), 0, "held across the whole nest");
|
||||
s.complete(great, Outcome::Done);
|
||||
assert_eq!(s.graph().node(r).unwrap().state, State::Done, "R rolled up");
|
||||
|
|
@ -984,10 +993,14 @@ mod tests {
|
|||
let mut s: Scheduler<&str, String> = Scheduler::new(Graph::new(), ResourceTable::new());
|
||||
let a = s.append("a", res_dep("agent/foo"), None).expect("a");
|
||||
let b = s.append("b", res_dep("agent/foo"), None).expect("b");
|
||||
assert_eq!(s.settle(), vec![a], "only a acquires; b can't borrow it");
|
||||
assert_eq!(
|
||||
settle(&mut s),
|
||||
vec![a],
|
||||
"only a acquires; b can't borrow it"
|
||||
);
|
||||
assert_eq!(s.graph().node(b).unwrap().state, State::Pending);
|
||||
s.complete(a, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![b]);
|
||||
assert_eq!(settle(&mut s), vec![b]);
|
||||
assert_eq!(s.graph().node(b).unwrap().state, State::Running);
|
||||
}
|
||||
|
||||
|
|
@ -1000,7 +1013,7 @@ mod tests {
|
|||
let owner = s
|
||||
.append("owner", res_dep("agent/foo"), None)
|
||||
.expect("owner");
|
||||
assert_eq!(s.settle(), vec![owner]);
|
||||
assert_eq!(settle(&mut s), vec![owner]);
|
||||
let c1 = s
|
||||
.append("c1", res_dep("agent/foo"), Some(owner))
|
||||
.expect("c1");
|
||||
|
|
@ -1008,10 +1021,10 @@ mod tests {
|
|||
.append("c2", res_dep("agent/foo"), Some(owner))
|
||||
.expect("c2");
|
||||
s.complete(owner, Outcome::Done); // → Finishing
|
||||
assert_eq!(s.settle(), vec![c1], "c1 borrows; c2 can't (cap 1)");
|
||||
assert_eq!(settle(&mut s), vec![c1], "c1 borrows; c2 can't (cap 1)");
|
||||
assert_eq!(s.graph().node(c2).unwrap().state, State::Pending);
|
||||
s.complete(c1, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![c2], "borrow returned → c2 borrows");
|
||||
assert_eq!(settle(&mut s), vec![c2], "borrow returned → c2 borrows");
|
||||
assert_eq!(avail(&s, "agent/foo"), 0, "still just the owner's unit");
|
||||
}
|
||||
|
||||
|
|
@ -1023,7 +1036,7 @@ mod tests {
|
|||
let owner = s
|
||||
.append("owner", res_dep("build-slot"), None)
|
||||
.expect("owner");
|
||||
assert_eq!(s.settle(), vec![owner]);
|
||||
assert_eq!(settle(&mut s), vec![owner]);
|
||||
assert_eq!(avail(&s, "build-slot"), 1, "owner took one of two");
|
||||
let c1 = s
|
||||
.append("c1", res_dep("build-slot"), Some(owner))
|
||||
|
|
@ -1032,7 +1045,7 @@ mod tests {
|
|||
.append("c2", res_dep("build-slot"), Some(owner))
|
||||
.expect("c2");
|
||||
s.complete(owner, Outcome::Done); // → Finishing
|
||||
let mut started = s.settle();
|
||||
let mut started = settle(&mut s);
|
||||
started.sort();
|
||||
let mut expected = vec![c1, c2];
|
||||
expected.sort();
|
||||
|
|
@ -1062,11 +1075,11 @@ mod tests {
|
|||
None,
|
||||
)
|
||||
.expect("weak");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Failed(String::new()));
|
||||
assert_eq!(s.graph().node(strong1).unwrap().state, State::Skipped);
|
||||
assert_eq!(s.graph().node(strong2).unwrap().state, State::Skipped);
|
||||
assert_eq!(s.settle(), vec![weak]);
|
||||
assert_eq!(settle(&mut s), vec![weak]);
|
||||
}
|
||||
|
||||
/// The direction only a *set* edge can express: a branch that runs solely on
|
||||
|
|
@ -1086,7 +1099,7 @@ mod tests {
|
|||
None,
|
||||
)
|
||||
.expect("compensate");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Done);
|
||||
assert_eq!(
|
||||
s.graph().node(on_fail).unwrap().state,
|
||||
|
|
@ -1094,7 +1107,7 @@ mod tests {
|
|||
"a Failed-only branch is unsatisfiable once its dep succeeds — and it is \
|
||||
`Skipped`, not `Cancelled`, so the parent roll-up ignores it"
|
||||
);
|
||||
assert!(s.settle().is_empty(), "and nothing is left runnable");
|
||||
assert!(settle(&mut s).is_empty(), "and nothing is left runnable");
|
||||
}
|
||||
|
||||
/// The mirror: the same branch is exactly what *does* run on failure, while
|
||||
|
|
@ -1116,10 +1129,10 @@ mod tests {
|
|||
None,
|
||||
)
|
||||
.expect("on_fail");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Failed("boom".to_owned()));
|
||||
assert_eq!(s.graph().node(on_ok).unwrap().state, State::Skipped);
|
||||
assert_eq!(s.settle(), vec![on_fail]);
|
||||
assert_eq!(settle(&mut s), vec![on_fail]);
|
||||
}
|
||||
|
||||
/// A weak edge accepts a dependency that was *ruled out*, so a tail still
|
||||
|
|
@ -1140,11 +1153,11 @@ mod tests {
|
|||
None,
|
||||
)
|
||||
.expect("tail");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Failed("boom".to_owned()));
|
||||
assert_eq!(s.graph().node(mid).unwrap().state, State::Skipped);
|
||||
assert_eq!(
|
||||
s.settle(),
|
||||
settle(&mut s),
|
||||
vec![tail],
|
||||
"the tail runs off a cancelled dependency"
|
||||
);
|
||||
|
|
@ -1181,22 +1194,26 @@ mod tests {
|
|||
|
||||
// Everything succeeds: the ok branch runs, the failure branch is ruled out.
|
||||
let (mut s, a, b, on_ok, on_fail) = build();
|
||||
assert_eq!(s.settle(), vec![a, b], "both roots start; neither tail can");
|
||||
assert_eq!(
|
||||
settle(&mut s),
|
||||
vec![a, b],
|
||||
"both roots start; neither tail can"
|
||||
);
|
||||
s.complete(a, Outcome::Done);
|
||||
s.complete(b, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![on_ok]);
|
||||
assert_eq!(settle(&mut s), vec![on_ok]);
|
||||
s.complete(on_ok, Outcome::Done);
|
||||
assert_eq!(s.graph().node(on_fail).unwrap().state, State::Skipped);
|
||||
assert!(s.settle().is_empty());
|
||||
assert!(settle(&mut s).is_empty());
|
||||
|
||||
// One of them fails: the ok branch is ruled out, which is precisely the
|
||||
// signal the failure branch waits on.
|
||||
let (mut s, a, b, on_ok, on_fail) = build();
|
||||
assert_eq!(s.settle(), vec![a, b]);
|
||||
assert_eq!(settle(&mut s), vec![a, b]);
|
||||
s.complete(a, Outcome::Failed("boom".to_owned()));
|
||||
s.complete(b, Outcome::Done);
|
||||
assert_eq!(s.graph().node(on_ok).unwrap().state, State::Skipped);
|
||||
assert_eq!(s.settle(), vec![on_fail]);
|
||||
assert_eq!(settle(&mut s), vec![on_fail]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1207,7 +1224,7 @@ mod tests {
|
|||
let root = s.append("root", vec![], None).expect("root");
|
||||
let child = s.append("child", vec![], Some(root)).expect("child");
|
||||
let grandchild = s.append("gc", vec![], Some(child)).expect("gc");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Failed(String::new()));
|
||||
assert_eq!(s.graph().node(child).unwrap().state, State::Skipped);
|
||||
assert_eq!(s.graph().node(grandchild).unwrap().state, State::Skipped);
|
||||
|
|
@ -1223,7 +1240,7 @@ mod tests {
|
|||
assert_eq!(s.graph().node(a).unwrap().state, State::Cancelled);
|
||||
assert_eq!(s.graph().node(b).unwrap().state, State::Skipped);
|
||||
let c = s.append("c", vec![], None).expect("c");
|
||||
assert_eq!(s.settle(), vec![c]);
|
||||
assert_eq!(settle(&mut s), vec![c]);
|
||||
assert!(!s.cancel_node(c));
|
||||
assert_eq!(s.graph().node(c).unwrap().state, State::Running);
|
||||
}
|
||||
|
|
@ -1239,7 +1256,7 @@ mod tests {
|
|||
let b = s.append("b", vec![after_ok(a)], Some(root)).expect("b");
|
||||
// The root runs first and parks in `Finishing` while its children are
|
||||
// outstanding — the state a group root is actually in when cancelled.
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Done);
|
||||
assert_eq!(s.graph().node(root).unwrap().state, State::Finishing);
|
||||
|
||||
|
|
@ -1260,9 +1277,9 @@ mod tests {
|
|||
let root = s.append("root", vec![], None).expect("root");
|
||||
let a = s.append("a", vec![], Some(root)).expect("a");
|
||||
let b = s.append("b", vec![after_ok(a)], Some(root)).expect("b");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Done);
|
||||
assert_eq!(s.settle(), vec![a], "a is claimed and running");
|
||||
assert_eq!(settle(&mut s), vec![a], "a is claimed and running");
|
||||
|
||||
assert!(!s.cancel_node(root), "refused while a runs");
|
||||
assert_eq!(s.graph().node(a).unwrap().state, State::Running);
|
||||
|
|
@ -1291,7 +1308,7 @@ mod tests {
|
|||
Some(root),
|
||||
)
|
||||
.expect("tail");
|
||||
assert_eq!(s.settle(), vec![root]);
|
||||
assert_eq!(settle(&mut s), vec![root]);
|
||||
s.complete(root, Outcome::Done);
|
||||
|
||||
assert!(s.cancel_node(root));
|
||||
|
|
@ -1301,7 +1318,7 @@ mod tests {
|
|||
State::Pending,
|
||||
"spared, and now runnable since its dep is Cancelled"
|
||||
);
|
||||
assert_eq!(s.settle(), vec![tail], "the tail still gets to report");
|
||||
assert_eq!(settle(&mut s), vec![tail], "the tail still gets to report");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1309,7 +1326,7 @@ mod tests {
|
|||
let mut s = scheduler_with_slots(1);
|
||||
let g = s.append("g", res_dep("agent/foo"), None).expect("g");
|
||||
let b = s.append("b", res_dep("build-slot"), None).expect("b");
|
||||
assert_eq!(s.settle().len(), 2);
|
||||
assert_eq!(settle(&mut s).len(), 2);
|
||||
let state = s.resource_state();
|
||||
assert!(state.contains(&(res("agent/foo"), g)));
|
||||
assert!(state.contains(&(res("build-slot"), b)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue