macro_rules! skip {
() => {{
$crate::report::test_skip(format_args!(""));
return;
}};
($($arg:tt)*) => {{
$crate::report::test_skip(format_args!($($arg)*));
return;
}};
}
macro_rules! skip_on_contention {
($expr:expr) => {
match $expr {
Ok(v) => v,
Err(e)
if e.chain().any(|cause| {
cause
.downcast_ref::<$crate::vmm::host_topology::ResourceContention>()
.is_some()
}) =>
{
skip!("resource contention: {e:#}");
}
Err(e)
if {
let msg = format!("{e:#}");
msg.contains("need") && (msg.contains("LLC") || msg.contains("CPU"))
} =>
{
skip!("host topology insufficient: {e:#}");
}
Err(e) => panic!("{e:#}"),
}
};
}
#[allow(unused_macros)]
macro_rules! require_capability {
($cap:expr) => {{
let ret = unsafe { libc::prctl(libc::PR_CAPBSET_READ, $cap, 0, 0, 0) };
if ret != 1 {
skip!(
"missing capability {} (prctl PR_CAPBSET_READ returned {})",
stringify!($cap),
ret
);
}
}};
}
#[allow(unused_macros)]
macro_rules! assert_not_impl_default {
($t:ty) => {
const _: fn() = || {
trait AmbiguousIfImpl<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfImpl<()> for T {}
#[allow(dead_code)]
struct InvalidDefault;
impl<T: ?Sized + Default> AmbiguousIfImpl<InvalidDefault> for T {}
let _ = <$t as AmbiguousIfImpl<_>>::some_item;
};
};
}
#[cfg(test)]
mod tests {
use crate::vmm::host_topology::ResourceContention;
#[test]
#[cfg(panic = "unwind")]
fn skip_on_contention_walks_context_chain() {
let result = std::panic::catch_unwind(|| {
fn skip_fn() {
let err: anyhow::Error = anyhow::Error::new(ResourceContention {
reason: "simulated contention".into(),
})
.context("wrapping context layer 1")
.context("wrapping context layer 2");
let _: () = skip_on_contention!(Err::<(), _>(err));
unreachable!("skip_on_contention! should have early-returned");
}
skip_fn();
});
assert!(
result.is_ok(),
"context-wrapped ResourceContention must skip, not panic"
);
}
#[test]
#[cfg(panic = "unwind")]
fn skip_on_contention_recognizes_direct_error() {
let result = std::panic::catch_unwind(|| {
fn skip_fn() {
let err: anyhow::Error = anyhow::Error::new(ResourceContention {
reason: "direct contention".into(),
});
let _: () = skip_on_contention!(Err::<(), _>(err));
unreachable!("skip_on_contention! should have early-returned");
}
skip_fn();
});
assert!(
result.is_ok(),
"direct ResourceContention must skip, not panic"
);
}
#[test]
#[should_panic(expected = "unrelated error")]
fn skip_on_contention_panics_on_non_contention_error() {
fn skip_fn() {
let err = anyhow::anyhow!("unrelated error");
let _: () = skip_on_contention!(Err::<(), _>(err));
}
skip_fn();
}
#[test]
fn skip_macro_emits_banner_and_early_returns() {
use crate::test_support::test_helpers::capture_stderr;
use std::sync::atomic::{AtomicBool, Ordering};
let reached_tail = AtomicBool::new(false);
let (_, bytes) = capture_stderr(|| {
#[allow(unused_variables, unreachable_code)]
fn helper(reached: &AtomicBool) {
skip!("macro-level reason with {} substitution", "format-args");
reached.store(true, Ordering::SeqCst);
}
helper(&reached_tail);
});
let text = std::str::from_utf8(&bytes).expect("stderr is UTF-8");
assert_eq!(
text, "ktstr: SKIP: macro-level reason with format-args substitution\n",
"expected canonical banner with format-args substitution",
);
assert!(
!reached_tail.load(Ordering::SeqCst),
"skip! must early-return; lines after the macro must not execute",
);
}
#[test]
fn skip_macro_literal_reason_emits_banner() {
use crate::test_support::test_helpers::capture_stderr;
let (_, bytes) = capture_stderr(|| {
fn helper() {
skip!("literal skip reason");
}
helper();
});
let text = std::str::from_utf8(&bytes).unwrap();
assert_eq!(text, "ktstr: SKIP: literal skip reason\n");
}
#[test]
fn skip_macro_zero_args_emits_banner_with_empty_reason() {
use crate::test_support::test_helpers::capture_stderr;
let (_, bytes) = capture_stderr(|| {
fn helper() {
skip!();
}
helper();
});
let text = std::str::from_utf8(&bytes).unwrap();
assert_eq!(text, "ktstr: SKIP: \n");
}
#[test]
#[cfg(panic = "unwind")]
fn ktstr_test_macro_body_skips_on_resource_contention() {
use crate::test_support::test_helpers::capture_stderr;
use crate::vmm::host_topology::ResourceContention;
use std::sync::atomic::{AtomicBool, Ordering};
let reached_tail = AtomicBool::new(false);
let result = std::panic::catch_unwind(|| {
let (_, bytes) = capture_stderr(|| {
#[allow(unused_variables, unreachable_code)]
fn helper(reached: &AtomicBool) {
let result: Result<(), anyhow::Error> =
Err(anyhow::Error::new(ResourceContention {
reason: "all 3 LLC slots busy".into(),
})
.context("build ktstr_test VM"));
match result {
Ok(_) => {}
Err(e) if crate::test_support::is_resource_contention(&e) => {
eprintln!("ktstr: SKIP: resource contention: {e:#}");
return;
}
Err(e) => panic!("{e:#}"),
}
reached.store(true, Ordering::SeqCst);
}
helper(&reached_tail);
});
let text = std::str::from_utf8(&bytes).expect("stderr is UTF-8");
assert!(
text.starts_with("ktstr: SKIP: resource contention: "),
"expected SKIP banner, got: {text:?}"
);
assert!(
text.contains("build ktstr_test VM"),
"banner must include the wrapping context layer; got: {text:?}"
);
assert!(
text.contains("all 3 LLC slots busy"),
"banner must include the inner ResourceContention reason; got: {text:?}"
);
});
assert!(
result.is_ok(),
"macro body must NOT panic on ResourceContention",
);
assert!(
!reached_tail.load(Ordering::SeqCst),
"macro body must early-return after emitting the SKIP banner",
);
}
}