pub struct TestDriver<P: Program, B: Backend> { /* private fields */ }Expand description
The same-topology scripted driver (RFC 0008 §9.3).
It drives a Program: a composed stack becomes one through
ReducerExt::into_program,
an Application through
AppProgram.
§Where a driver test runs
On a plain #[test]. Turning the executor blocks the calling thread and
so does dropping it, and Tokio refuses to block a thread that is itself
driving a runtime’s tasks. That is narrower than being in one: a thread
holding an entered handle, a worker inside block_in_place, and a
spawn_blocking thread all satisfy Handle::try_current and take a
driver. Construction blocks on nothing and so refuses nothing: a driver
built under #[tokio::test] is built, and fails at the first call that
blocks or at the drop.
TestStore rejects an ambient runtime at
construction. That is INV-T10’s, and INV-T10 is the store’s alone.
Implementations§
Source§impl<P: Program, B: Backend> TestDriver<P, B>
impl<P: Program, B: Backend> TestDriver<P, B>
Sourcepub fn new(
program: P,
flags: P::Flags,
config: RuntimeConfig,
terminal: Terminal<B>,
) -> Self
pub fn new( program: P, flags: P::Flags, config: RuntimeConfig, terminal: Terminal<B>, ) -> Self
Inert construction from the production entry point’s inputs (RFC 0014 §2.3), owning a terminal to render into.
Nothing starts until boot: construction is inert for
both production entry points (RFC 0011 INV-LC3) and is inert here for
the same reason. The terminal is a real ratatui::Terminal, so
Program::view runs exactly as it does in production and there is no
way for the driver to reach a frame without going through it.
§Panics
Panics when the executor cannot be built. An ambient Tokio runtime is not rejected here: construction blocks on nothing.
Sourcepub fn on_worker_threads(
program: P,
flags: P::Flags,
config: RuntimeConfig,
terminal: Terminal<B>,
workers: NonZeroUsize,
) -> Self
pub fn on_worker_threads( program: P, flags: P::Flags, config: RuntimeConfig, terminal: Terminal<B>, workers: NonZeroUsize, ) -> Self
The same construction on a multi-worker executor, for the series that need a producer to commit while a pass is running.
This is harness plumbing, not a second seam. The executor a driver turns is mechanism: §9.8 scopes INV-RC14’s determinism claim to a current-thread executor — the verified range — rather than fixing the driver’s own construction. Nothing else differs: the same production construction path, the same two seams, the same pass-unit stepping.
What it buys is the one window a single-threaded executor does not have. A pass is a synchronous region — RFC 0014 §3.5’s four stages run without the driving task yielding — so on one thread no producer can commit inside a pass, and INV-RC9’s mid-batch and cancel-beats-quit rows have nothing to observe. With worker threads beside the driving thread they do, and the series that use this carry their own determinism: the commit is synchronized to a stage boundary by an application-side handshake, never by the scheduler, and those series cite no part of INV-RC14.
The driving contract carries this constructor: RFC 0008 §9.3’s block states it, on the footing §9.12 gives it — it drives what the determinism claim’s verified range excludes, and cites no part of that claim. What stays open is the executor-independence question itself (RFC 0014 §13.3), to which these runs are an input rather than an answer.
§Panics
Panics when the multi-worker executor cannot be built. Like
new, it blocks on nothing.
Sourcepub fn boot(&mut self) -> StepReport<B::Error>
pub fn boot(&mut self) -> StepReport<B::Error>
Runs the production bootstrap through to a parked kernel (RFC 0008 §9.5): the intake order, then the continuation pass that consumes the pending first render.
Absent a termination this returns with that render consumed and no
lane item outstanding — no grant has released a send — so the kernel
is in the state production reaches by the same route, and the next
step names one of the three sources that can wake it. An init command
carrying Command::quit() terminates during the init dispatch,
before the initial reconcile and before any render (RFC 0014 §6.2),
so the report carries the termination and the continuation pass never
ran.
§Panics
Panics when the driver has already booted: the state table admits
boot in exactly one position.
Panics too when called on a thread that is driving a Tokio runtime’s tasks, which Tokio refuses rather than this layer.
Panics too when the step terminates the program and the terminated kernel’s join set does not drain inside the settle bound — the one wait this layer chooses for itself rather than taking from a script, whose budget is mechanism: what a caller can rely on is that it is finite and fails on its bound.
Sourcepub fn step_pass(
&mut self,
woken_by: WakeSource,
) -> Result<StepReport<B::Error>, NotReady>
pub fn step_pass( &mut self, woken_by: WakeSource, ) -> Result<StepReport<B::Error>, NotReady>
Executes one whole production pass — RFC 0014 §3.5’s four stages in
their fixed order — begun by woken_by.
Drives nothing and returns Err(NotReady) when that source has not
arrived: readiness is read from the production lanes and the
production join set, never scripted (RFC 0008 §9.5). One call is one
whole pass; no method runs a stage of one.
§Errors
Returns NotReady when the named source has nothing, which is a
production fact rather than misuse and leaves the driver untouched.
§Panics
Panics when the driver has not booted or has terminated (RFC 0008 §9.3’s state table).
Panics too when called on a thread that is driving a Tokio runtime’s tasks, which Tokio refuses rather than this layer. A refused step is no way out: the readiness read happens inside the block.
Panics too when the step terminates the program and the terminated kernel’s join set does not drain inside the settle bound — the one wait this layer chooses for itself rather than taking from a script, whose budget is mechanism: what a caller can rely on is that it is finite and fails on its bound.
Sourcepub fn grant(&mut self, run: RunName) -> Result<GrantToken, GrantOutstanding>
pub fn grant(&mut self, run: RunName) -> Result<GrantToken, GrantOutstanding>
Arms a grant at run, releasing the next of that run’s send-intents
that no grant has released yet — one already waiting at the gate when
this is called, or else the first to arrive after it (RFC 0008 §9.6).
A grant is therefore issuable ahead of the producer that will satisfy it, which is what lets a script fix an order before the run reaches its send point. The returned token borrows neither the driver nor the script. At most one grant is outstanding across the whole driver; the next — at this run or any other — is admitted only after this one resolves.
§Errors
Returns GrantOutstanding while a grant is unresolved, whatever
run this one names. That is a script-order fact rather than misuse,
and it leaves the driver untouched.
§Panics
Panics at a run the kernel’s bookkeeping does not currently hold — a run never started, or one whose exit a pass has already reflected. That is a script error the kernel can produce no outcome for, and an error return would let a test go on scripting against a run that is gone. Panics too outside the running state.
Sourcepub fn confirm(&mut self, max_turns: usize, token: GrantToken) -> Confirmed
pub fn confirm(&mut self, max_turns: usize, token: GrantToken) -> Confirmed
Consumes token, driving the executor — beginning no pass — for at
most max_turns turns, until the grant resolves, and reports how
(RFC 0008 §9.6).
A grant ends in one of exactly two states, and the two facts that establish the second are read from different places. The gate holds the first — a released send that ended without getting into the lane records its own terminal there. The second is the kernel’s: the granted run’s exit is reflected in the run bookkeeping and this grant released no send at all, so nothing is left that could arrive. The gate cannot see that one, so this call reads the bookkeeping and clears the grant on it.
The budget is the caller’s, as settle’s is, and
for the same reason: a producer granted a release may take several
turns to reach its send, so a bound of one can report exhaustion
where a bound of three reports Confirmed::Accepted on the same
finite execution. A driver-chosen bound would make conformance depend
on a mechanism, so the number is named at the call site and is an
element of the script (RFC 0008 §9.8). What is not the caller’s is
the completion condition: that one is the gate’s — the grant
resolving one way or the other.
The disjunction is this call’s completion condition, not a promise that one of its arms arrives inside the budget. Two of the ways a grant ends need a pass first, and in both the test steps and confirms after: a commit that waits on lane capacity needs the pass that drains the lane ahead of it, and a run’s exit reaches the kernel’s bookkeeping only at stage 1 of a pass. The token is detached exactly so it survives the step that makes the resolution reachable.
§Panics
Panics when max_turns is spent with the grant unresolved, reporting
how many turns were consumed, and outside the running state. Panics
too when it turns the executor on a thread that is driving a Tokio
runtime’s tasks: the turn is where it blocks.
Sourcepub fn try_confirm(&self, token: &GrantToken) -> Option<Confirmed>
pub fn try_confirm(&self, token: &GrantToken) -> Option<Confirmed>
Whether the grant token names has reached a terminal yet, without
consuming the token, spending a turn, or clearing the grant.
It exists because the rest of the surface has no non-destructive
way to ask the question: confirm consumes the
token and fails on
its budget, so a row that wants to assert “this release has not
committed yet” and then go on driving has nothing to assert with —
and a row that instead reads the acceptance ledger asserts nothing at
all, since a grant that has not been taken leaves the ledger
unchanged for reasons that have nothing to do with the lane. The
driving contract carries it for that reason: RFC 0008 §9.3’s block
states it, and §9.12 records the bounded-lane verification pass it
made witnessable.
It reports the terminal the gate holds. A grant that released no
send has none, so this is None there — the second reclaiming fact
is the caller’s to establish and confirm is where it is reported.
The receiver is &self, with the observation calls rather than the
driving ones: this turns nothing, takes nothing, and clears nothing.
The normative &mut self spelling belongs to RFC 0008 §9.3’s block,
which this is outside of, and claiming exclusivity it does not need
would misreport what the call is.
§Panics
Panics outside the running state, and on a token this driver’s gate no longer holds.
Sourcepub fn settle(&mut self, max_turns: usize, until: impl FnMut() -> bool)
pub fn settle(&mut self, max_turns: usize, until: impl FnMut() -> bool)
Drives the executor — beginning no pass and releasing no send-intent
— until until holds (RFC 0008 §9.6).
Some runs finish without ever presenting a send-intent: a cleanup
finalizer whose Output = () closes the message path outright, a
future that completes with no message, a subscription run stopping
after its last output. No grant releases them, and no pass guarantees
them anything — a pass that never awaits yields no turns at all. That
is the gap this call closes, with turns as its purpose, a stated
budget, and a completion condition.
Both the budget and the completion condition are the caller’s,
and both are elements of the script. until is evaluated once before
the first turn — so a condition already true costs no turns — and
again after each turn, for at most max_turns of them. What the
predicate can see is what the test can see, which is ordinarily the
test’s own application-side instrumentation: a finalizer that sets a
flag, a mock source that records its stop. It is deliberately not a
run’s exit as the driver knows it, which reaches the driver only at a
pass’s first stage.
A turn has one construction: the driving task spawns a fresh no-op task onto this driver’s executor and awaits it. A turn is a unit of opportunity, not of progress — it says nothing about how far any task runs, in what order tasks are picked, or whether one completes. Informatively, on the current-thread executor this driver owns, a FIFO ready queue means the tasks ready at the spawn do in practice run before the join resolves; that is an observation about that executor rather than a promise (RFC 0008 §9.6, §9.8).
This call initiates no append to the guaranteed sequence, and that holds structurally rather than by intent: it is misuse while a grant is outstanding, and no send is released except through one. The intent ledger may gain entries all the same, from any producer the turns advance to a send point — as it may during any driving call.
§Panics
Panics while a grant is outstanding, stranded or not; panics when
max_turns is spent with until still false, reporting how many
turns were consumed; and panics outside the running state. Panics too
when it turns the executor on a thread that is driving a Tokio
runtime’s tasks: the turn is where it blocks.
Sourcepub fn accepted(&self) -> AcceptanceLedger
pub fn accepted(&self) -> AcceptanceLedger
Sends admitted past the gate, in gate order. Admission, not delivery (RFC 0008 §9.6).
Legal in every state, and empty before boot.
§Panics
Panics on a record naming a run the driver never observed starting, which would be the naming rule failing rather than a test error.
Sourcepub fn intents(&self) -> IntentLedger
pub fn intents(&self) -> IntentLedger
Send-intents recorded before the gate, under no ordering or completeness guarantee (RFC 0008 §9.6).
Legal in every state, and empty before boot.
§Panics
Panics on a record naming a run the driver never observed starting.
Auto Trait Implementations§
impl<P, B> !Freeze for TestDriver<P, B>
impl<P, B> !RefUnwindSafe for TestDriver<P, B>
impl<P, B> !Sync for TestDriver<P, B>
impl<P, B> !UnwindSafe for TestDriver<P, B>
impl<P, B> Send for TestDriver<P, B>
impl<P, B> Unpin for TestDriver<P, B>
impl<P, B> UnsafeUnpin for TestDriver<P, B>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more