use syn::{Attribute, Lit, Meta};
use crate::cli::RuntimeChoice;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TestKind {
Actix,
AsyncStd,
Compio,
FuturesTest,
PlainTest,
TokioCurrent,
TokioDefault,
TokioMulti,
}
impl TestKind {
#[inline]
#[must_use]
pub const fn forced_runtime(self) -> Option<RuntimeChoice> {
match self {
Self::Compio => Some(RuntimeChoice::Compio),
Self::TokioCurrent => Some(RuntimeChoice::TokioCt),
Self::TokioDefault | Self::TokioMulti => Some(RuntimeChoice::TokioMt),
Self::Actix | Self::AsyncStd | Self::FuturesTest | Self::PlainTest => None,
}
}
#[inline]
#[must_use]
pub const fn needs_compat_warning(self) -> Option<&'static str> {
match self {
Self::Actix => Some(
"actix runtime replaced with the --runtime default; actix-rt actor setup must be added manually if tests depend on it",
),
Self::AsyncStd => Some(
"async-std runtime replaced with the --runtime default; async-std-specific APIs may not behave identically",
),
Self::FuturesTest => Some("futures-test runtime replaced with the --runtime default"),
Self::Compio
| Self::PlainTest
| Self::TokioCurrent
| Self::TokioDefault
| Self::TokioMulti => None,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Detected {
pub extra_tokio_args: Vec<String>,
pub kind: TestKind,
}
#[inline]
#[must_use]
pub fn as_test_context(attr: &Attribute) -> Option<syn::Path> {
if path_to_string(attr.path()) != "test_context" {
return None;
}
let Meta::List(list) = &attr.meta else {
return None;
};
list.parse_args::<syn::Path>().ok()
}
#[inline]
#[must_use]
pub fn classify_test_attr(attr: &Attribute) -> Option<Detected> {
let path = path_to_string(attr.path());
let kind = match path.as_str() {
"test" => TestKind::PlainTest,
"tokio::test" | "::tokio::test" => {
return Some(classify_tokio_attr(attr));
}
"async_std::test" | "::async_std::test" => TestKind::AsyncStd,
"compio::test" | "::compio::test" => TestKind::Compio,
"actix_rt::test" | "::actix_rt::test" | "actix_web::test" | "::actix_web::test" => {
TestKind::Actix
}
"futures_test::test" | "::futures_test::test" => TestKind::FuturesTest,
_ => return None,
};
Some(Detected {
kind,
extra_tokio_args: Vec::new(),
})
}
fn classify_tokio_attr(attr: &Attribute) -> Detected {
let mut kind = TestKind::TokioDefault;
let mut extras = Vec::new();
if let Meta::List(list) = &attr.meta {
let tokens = list.tokens.to_string();
if tokens.contains("\"multi_thread\"") {
kind = TestKind::TokioMulti;
} else if tokens.contains("\"current_thread\"") {
kind = TestKind::TokioCurrent;
} else {
}
let _unused = attr.parse_nested_meta(|meta| {
let name = meta.path.get_ident().map(ToString::to_string);
match name.as_deref() {
Some("flavor") => {
let _unused = meta
.value()
.and_then(syn::parse::ParseBuffer::parse::<syn::LitStr>);
}
Some("worker_threads") => {
if let Ok(parsed) = meta.value().and_then(syn::parse::ParseBuffer::parse::<Lit>)
{
extras.push(format!("worker_threads = {}", lit_to_string(&parsed)));
}
}
Some("start_paused") => {
if let Ok(parsed) = meta.value().and_then(syn::parse::ParseBuffer::parse::<Lit>)
{
extras.push(format!("start_paused = {}", lit_to_string(&parsed)));
}
}
_ => {
}
}
Ok(())
});
}
Detected {
kind,
extra_tokio_args: extras,
}
}
#[inline]
#[must_use]
pub fn is_bench_attr(attr: &Attribute) -> bool {
path_to_string(attr.path()) == "bench"
}
#[inline]
#[must_use]
pub fn is_ignore_attr(attr: &Attribute) -> bool {
path_to_string(attr.path()) == "ignore"
}
#[inline]
#[must_use]
pub fn is_rstest_attr(attr: &Attribute) -> bool {
let path = path_to_string(attr.path());
matches!(
path.as_str(),
"rstest"
| "::rstest::rstest"
| "rstest::rstest"
| "case"
| "::rstest::case"
| "rstest::case"
| "values"
| "::rstest::values"
| "rstest::values"
)
}
#[inline]
#[must_use]
pub fn is_should_panic_attr(attr: &Attribute) -> bool {
path_to_string(attr.path()) == "should_panic"
}
#[inline]
#[must_use]
pub fn lit_to_string(lit: &Lit) -> String {
match lit {
Lit::Bool(boolean) => boolean.value.to_string(),
Lit::Int(int) => int.base10_digits().to_owned(),
Lit::Str(string) => format!("\"{}\"", string.value()),
other @ (Lit::Byte(_)
| Lit::ByteStr(_)
| Lit::CStr(_)
| Lit::Char(_)
| Lit::Float(_)
| Lit::Verbatim(_))
| other => quote::ToTokens::to_token_stream(other).to_string(),
}
}
#[inline]
#[must_use]
pub fn path_to_string(path: &syn::Path) -> String {
let mut out = String::new();
if path.leading_colon.is_some() {
out.push_str("::");
}
for (idx, seg) in path.segments.iter().enumerate() {
if idx > 0 {
out.push_str("::");
}
out.push_str(&seg.ident.to_string());
}
out
}