pub enum Outcome {
Pass,
Skip(AssertDetail),
Inconclusive(AssertDetail),
Fail(AssertDetail),
}Expand description
Terminal verdict for a single test scenario or merge fold —
strict four-state enum that replaces the (passed, skipped)
bool-pair encoding on AssertResult.
Precedence under AssertResult::merge:
Fail > Inconclusive > Pass > Skip.
A merge that contains any Fail resolves to Fail; absent a
Fail, any Inconclusive resolves to Inconclusive; absent
both, a Pass + Skip mix resolves to Pass (Pass dominates
Skip — a check that actually ran and passed overrides a
sibling check whose precondition was unmet, so the merge does
not falsely demote to Skip on the strength of an unrelated
missing-precondition sibling). Skip-only merges stay Skip.
Pass-only merges stay Pass. Inconclusive sits between Fail
and Pass because “couldn’t evaluate” is not a real Pass (an
Inconclusive run must not satisfy is_pass()-keyed CI gates)
but also not a hard Fail (no claim was made that the system
did the wrong thing).
Inconclusive exists for ratio assertions whose denominator
is an INSTRUMENT-derived measurement (iteration count, sample
count, wall-clock interval) that legitimately reached zero —
the gate has no signal to evaluate against. Distinguish from
Fail: a POLICY-derived denominator (e.g. NUMA pages under
MemPolicy::Bind, where the policy specifies pages will
exist) staying at zero IS a defect signal and stays as Fail
per the existing semantic — see assert_page_locality /
AssertPlan::assert_cgroup for the policy-derived carve-out.
Note: Notes do NOT belong here. AssertResult::info_notes
is the structurally-separate context stream; re-encoding Note
as an Outcome variant would re-mix the failure / verdict
surface with the context surface and erase the separation.
Outcome is strictly terminal verdict; notes are non-verdict
context.
Skip, Inconclusive, and Fail carry an AssertDetail
payload so the match arm has the diagnostic in hand without
re-walking details. Pass carries no payload — there is no
failure to describe.
Outcomes are stored as AssertResult::outcomes and the
AssertResult::outcome accessor folds the vec via this enum’s
Self::merge (identity = Outcome::Pass). Callers query via
AssertResult::is_pass / AssertResult::is_fail /
AssertResult::is_skip / AssertResult::is_inconclusive
(bool checks), AssertResult::record_fail /
AssertResult::record_skip / AssertResult::record_pass /
AssertResult::record_inconclusive (atomic mutators), or
AssertResult::failure_details / AssertResult::skip_details /
AssertResult::inconclusive_details (per-variant payload
iterators).
Skip is not Pass: is_pass() returns false on skip — a
skipped scenario is “couldn’t run”, not “passed”. Stats tooling
and gate callers that want to count “not a failure” must test
r.is_pass() || r.is_skip() rather than bare r.is_pass().
Inconclusive is not Pass either: is_pass() returns false
when any Inconclusive is recorded, so a zero-denominator ratio
gate cannot silently satisfy an is_pass()-keyed CI check.
Uses serde’s externally-tagged default (no #[serde(tag, content)]). Most ktstr enums adopt the adjacently-tagged
#[serde(tag = "kind", content = "data")] style for JSON
readability, but Outcome is uniquely wire-encoded via
postcard as part of AssertResult’s TLV transport from
guest to host (see
crate::test_support::output::parse_assert_result_from_drain
and crate::test_support::test_helpers::assert_result_tlv_entry).
Postcard is not a self-describing format and cannot decode
adjacently-tagged enums — pre-fix the decode silently failed and
surfaced as ERR_NO_TEST_FUNCTION_OUTPUT. The externally-tagged
default is what postcard’s externally-tagged enum decoder
expects. tests_assert.rs::outcome_serde_externally_tagged_*
pins both the JSON shape and the postcard roundtrip so a
refactor that re-adds adjacent tagging trips loudly at test
time rather than at runtime.
§Wire-format stability (postcard variant index)
Postcard encodes externally-tagged enums by variant index,
not variant name — the integer position in the enum body
becomes part of the wire format. The current encoding is:
Pass=0, Skip=1, Inconclusive=2, Fail=3.
Append-only: new variants MUST be added at the END of the
variant list. Re-ordering, removing, or inserting a variant
shifts the index of every variant after it and silently
reinterprets in-flight bytes from guest payloads as a
different variant on the host — the failure mode is a Pass
reading as Skip (or vice versa) with no decode error.
Any change to the variant order or list MUST be accompanied
by an update to tests_assert.rs::outcome_serde_externally_tagged_*
(which pins both the JSON shape and the postcard byte
sequence) so a silent-shift regression trips at test time.
Variants§
Implementations§
Source§impl Outcome
impl Outcome
Sourcepub fn is_pass(&self) -> bool
pub fn is_pass(&self) -> bool
True iff self == Outcome::Pass.
Part of the is_pass / is_fail / is_inconclusive /
is_skip vocabulary uniform across the verdict surfaces:
crate::assert::AssertResult::is_pass /
crate::test_support::SidecarResult::is_pass /
Self::is_pass / MonitorVerdict::is_pass (in the
monitor module, which is pub(crate)) / Verdict::is_pass
(re-exported at crate::assert::Verdict) /
GauntletRow::is_pass (in the stats module, which is
pub(crate)). OutcomeRef::is_pass is a borrowed-view
twin of this method on the borrowed OutcomeRef enum and
is intentionally NOT counted as a peer surface — it shares
the boolean semantic for naming parity but is a &self
projection over Outcome, not an independent verdict
shape.
Sourcepub fn is_inconclusive(&self) -> bool
pub fn is_inconclusive(&self) -> bool
True iff self == Outcome::Inconclusive(_).
Sourcepub fn merge(self, other: Outcome) -> Outcome
pub fn merge(self, other: Outcome) -> Outcome
Merge two outcomes per the precedence
Fail > Inconclusive > Pass > Skip.
Discriminant-commutative: the merged Pass/Skip/Inconclusive/Fail
kind is the same regardless of operand order. Idempotent on
Pass (Pass.merge(Pass) == Pass).
Payload semantic (NOT commutative):
- Same-variant ties (Fail+Fail, Inconclusive+Inconclusive, Skip+Skip): the LEFT operand’s payload wins, so caller- controlled merge ordering produces deterministic detail content.
- Cross-variant Fail+{Inconclusive,Skip}: the merged outcome is Fail and the payload comes from whichever side carries the Fail (the dominated side’s payload is dropped — the merged verdict is Fail, so the dominated narrative is irrelevant to the failure record).
- Cross-variant Inconclusive+{Pass,Skip}: merged outcome is Inconclusive and the payload comes from whichever side carries the Inconclusive.
Sourcepub fn as_ref(&self) -> OutcomeRef<'_>
pub fn as_ref(&self) -> OutcomeRef<'_>
Borrow this outcome’s payload as an OutcomeRef. Zero-
allocation projection — Pass carries no payload; Skip,
Inconclusive, and Fail borrow their AssertDetail in
place. Used by
the verdict-read fast path
(AssertResult::outcome_ref) and any caller that wants
to inspect the terminal verdict without cloning the
detail (e.g. error-message formatting where the detail
outlives the formatter, or sidecar emission that already
owns the source Outcome).
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Outcome
impl<'de> Deserialize<'de> for Outcome
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Outcome
impl StructuralPartialEq for Outcome
Auto Trait Implementations§
impl Freeze for Outcome
impl RefUnwindSafe for Outcome
impl Send for Outcome
impl Sync for Outcome
impl Unpin for Outcome
impl UnwindSafe for Outcome
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§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