use super::*;
pub(crate) const LLM_MODEL_LOAD_FAILED_PREFIX: &str = "LlmExtract model load failed: ";
pub(crate) fn should_skip_on_llm_model_load_failure(
host_extract_failures: &[crate::assert::AssertDetail],
post_vm_failed: bool,
) -> Option<String> {
if post_vm_failed {
return None;
}
host_extract_failures
.iter()
.find(|d| d.message.starts_with(LLM_MODEL_LOAD_FAILED_PREFIX))
.map(|d| d.message.clone())
}
#[cfg(test)]
mod should_skip_on_llm_model_load_failure_tests {
use super::{LLM_MODEL_LOAD_FAILED_PREFIX, should_skip_on_llm_model_load_failure};
use crate::assert::{AssertDetail, DetailKind};
fn model_load_failure() -> AssertDetail {
AssertDetail::new(
DetailKind::Other,
format!("{LLM_MODEL_LOAD_FAILED_PREFIX}cold-cache offline"),
)
}
#[test]
fn model_load_failure_no_post_vm_skips() {
let failures = vec![model_load_failure()];
assert!(should_skip_on_llm_model_load_failure(&failures, false).is_some());
}
#[test]
fn model_load_failure_with_post_vm_does_not_skip() {
let failures = vec![model_load_failure()];
assert!(should_skip_on_llm_model_load_failure(&failures, true).is_none());
}
#[test]
fn non_model_failure_does_not_skip() {
let failures = vec![AssertDetail::new(
DetailKind::Other,
"metric out of declared range".to_string(),
)];
assert!(should_skip_on_llm_model_load_failure(&failures, false).is_none());
}
#[test]
fn no_failures_does_not_skip() {
assert!(should_skip_on_llm_model_load_failure(&[], false).is_none());
}
#[test]
fn model_load_failure_among_others_skips() {
let failures = vec![
AssertDetail::new(
DetailKind::Other,
"metric out of declared range".to_string(),
),
model_load_failure(),
];
let skip = should_skip_on_llm_model_load_failure(&failures, false);
assert!(
skip.as_deref()
.is_some_and(|m| m.starts_with(LLM_MODEL_LOAD_FAILED_PREFIX)),
"the model-load detail (2nd in the vec) must be found + returned; got {skip:?}",
);
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ScxBpfErrorMatcherMismatch;
impl std::fmt::Display for ScxBpfErrorMatcherMismatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"scx_bpf_error matcher mismatch — the reproducer matcher rejected \
this failure mode; expect_err inversion bypassed"
)
}
}
impl std::error::Error for ScxBpfErrorMatcherMismatch {}
#[derive(Debug, Clone, Copy)]
pub(crate) struct PostVmAssertionFailure;
impl std::fmt::Display for PostVmAssertionFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"host-side post_vm assertion failed — expect_err inversion bypassed \
(a host-side check is honored even when the accompanying guest-side \
failure is expected)"
)
}
}
impl std::error::Error for PostVmAssertionFailure {}
#[derive(Debug, Clone, Copy)]
pub(crate) struct HostSkipRequest;
impl std::fmt::Display for HostSkipRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"host-side post_vm requested skip — the run is inconclusive \
(the VM could not produce the artifact the assertion needs)"
)
}
}
impl std::error::Error for HostSkipRequest {}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ExpectAutoReproSatisfied;
impl std::fmt::Display for ExpectAutoReproSatisfied {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"expect_auto_repro satisfied — the primary test failed and the \
auto-repro VM produced a shape-valid .repro.wprof.pb artifact; \
verdict inverted to PASS"
)
}
}
impl std::error::Error for ExpectAutoReproSatisfied {}
pub(crate) fn combine_post_vm_errs(
conditional: Option<anyhow::Error>,
unconditional: Option<anyhow::Error>,
) -> Option<anyhow::Error> {
match (conditional, unconditional) {
(Some(c), Some(u)) => {
let both_skip = c.downcast_ref::<HostSkipRequest>().is_some()
&& u.downcast_ref::<HostSkipRequest>().is_some();
let combined = anyhow::anyhow!("post_vm: {c:#}; post_vm_unconditional: {u:#}");
Some(if both_skip {
combined.context(HostSkipRequest)
} else {
combined
})
}
(Some(c), None) => Some(c),
(None, Some(u)) => Some(u),
(None, None) => None,
}
}
pub fn post_vm_skip(reason: impl Into<String>) -> anyhow::Error {
anyhow::anyhow!("{}", reason.into()).context(HostSkipRequest)
}
#[cfg(test)]
mod post_vm_skip_tests {
use super::{HostSkipRequest, PostVmAssertionFailure, combine_post_vm_errs, post_vm_skip};
fn real_fail() -> anyhow::Error {
anyhow::anyhow!("real host-side regression").context(PostVmAssertionFailure)
}
#[test]
fn post_vm_skip_carries_marker() {
assert!(
post_vm_skip("inconclusive: placeholder dump")
.downcast_ref::<HostSkipRequest>()
.is_some()
);
}
#[test]
fn combine_lone_unconditional_skip_preserved() {
let c = combine_post_vm_errs(None, Some(post_vm_skip("ph"))).unwrap();
assert!(c.downcast_ref::<HostSkipRequest>().is_some());
}
#[test]
fn combine_lone_conditional_skip_preserved() {
let c = combine_post_vm_errs(Some(post_vm_skip("ph")), None).unwrap();
assert!(c.downcast_ref::<HostSkipRequest>().is_some());
}
#[test]
fn combine_both_skip_yields_skip() {
let c = combine_post_vm_errs(Some(post_vm_skip("a")), Some(post_vm_skip("b"))).unwrap();
assert!(c.downcast_ref::<HostSkipRequest>().is_some());
}
#[test]
fn combine_skip_plus_real_fail_does_not_skip() {
let c = combine_post_vm_errs(Some(post_vm_skip("ph")), Some(real_fail())).unwrap();
assert!(c.downcast_ref::<HostSkipRequest>().is_none());
}
#[test]
fn combine_real_fail_plus_skip_does_not_skip() {
let c = combine_post_vm_errs(Some(real_fail()), Some(post_vm_skip("ph"))).unwrap();
assert!(c.downcast_ref::<HostSkipRequest>().is_none());
}
}
pub(crate) fn run_post_vm_callbacks(
entry: &KtstrTestEntry,
result: &crate::vmm::VmResult,
guest_already_failed: bool,
) -> Option<anyhow::Error> {
let conditional = if guest_already_failed {
None
} else {
entry
.post_vm
.and_then(|cb| invoke_post_vm_callback(cb, result, "post_vm"))
};
let unconditional = entry
.post_vm_unconditional
.and_then(|cb| invoke_post_vm_callback(cb, result, "post_vm_unconditional"));
combine_post_vm_errs(conditional, unconditional)
}
pub(crate) fn invoke_post_vm_callback(
cb: super::super::PostVmCallback,
result: &crate::vmm::VmResult,
label: &'static str,
) -> Option<anyhow::Error> {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(result))) {
Ok(Ok(())) => None,
Ok(Err(e)) => Some(e),
Err(payload) => {
let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
};
Some(anyhow::anyhow!("{label} callback panicked: {msg}"))
}
}
}
pub(crate) fn record_skip_sidecar(entry: &KtstrTestEntry) {
if let Err(e) = write_skip_sidecar(entry) {
let entry_name = entry.name;
let rendered = format!("{e:#}");
eprintln!("ktstr_test: warn: skip-sidecar write failed for {entry_name}: {rendered}");
tracing::warn!(
test = %entry_name,
err = %rendered,
"skip-sidecar write failed — stats tooling will not see this skip",
);
}
}