use crate::attributes::{Attribute, AttributePath};
const RSTEST_TEST_PATHS: &[&[&str]] = &[&["rstest"], &["rstest", "rstest"]];
const RSTEST_FIXTURE_PATHS: &[&[&str]] = &[&["fixture"], &["rstest", "fixture"]];
const DEFAULT_PROVIDER_ATTRIBUTE_PATHS: &[&[&str]] = &[
&["case"],
&["rstest", "case"],
&["values"],
&["rstest", "values"],
&["files"],
&["rstest", "files"],
&["future"],
&["rstest", "future"],
&["context"],
&["rstest", "context"],
];
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ExpansionTrace {
frames: Vec<AttributePath>,
}
impl ExpansionTrace {
#[must_use]
pub fn new<I>(frames: I) -> Self
where
I: IntoIterator<Item = AttributePath>,
{
Self {
frames: frames.into_iter().collect(),
}
}
#[must_use]
pub fn frames(&self) -> &[AttributePath] {
&self.frames
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RstestDetectionOptions {
provider_param_attributes: Vec<AttributePath>,
use_expansion_trace_fallback: bool,
}
impl RstestDetectionOptions {
#[must_use]
pub fn new(
provider_param_attributes: Vec<AttributePath>,
use_expansion_trace_fallback: bool,
) -> Self {
Self {
provider_param_attributes,
use_expansion_trace_fallback,
}
}
#[must_use]
pub fn provider_param_attributes(&self) -> &[AttributePath] {
&self.provider_param_attributes
}
#[must_use]
pub const fn use_expansion_trace_fallback(&self) -> bool {
self.use_expansion_trace_fallback
}
}
impl Default for RstestDetectionOptions {
fn default() -> Self {
Self::new(
DEFAULT_PROVIDER_ATTRIBUTE_PATHS
.iter()
.map(|path| AttributePath::new(path.iter().copied()))
.collect(),
false,
)
}
}
#[must_use]
pub fn is_rstest_test(attrs: &[Attribute]) -> bool {
has_matching_attribute(attrs, RSTEST_TEST_PATHS)
}
#[must_use]
pub fn is_rstest_test_with(
attrs: &[Attribute],
trace: Option<&ExpansionTrace>,
options: &RstestDetectionOptions,
) -> bool {
matches_direct_or_trace(attrs, trace, options, RSTEST_TEST_PATHS)
}
#[must_use]
pub fn is_rstest_fixture(attrs: &[Attribute]) -> bool {
has_matching_attribute(attrs, RSTEST_FIXTURE_PATHS)
}
#[must_use]
pub fn is_rstest_fixture_with(
attrs: &[Attribute],
trace: Option<&ExpansionTrace>,
options: &RstestDetectionOptions,
) -> bool {
matches_direct_or_trace(attrs, trace, options, RSTEST_FIXTURE_PATHS)
}
fn matches_direct_or_trace(
attrs: &[Attribute],
trace: Option<&ExpansionTrace>,
options: &RstestDetectionOptions,
candidates: &[&[&str]],
) -> bool {
has_matching_attribute(attrs, candidates)
|| (options.use_expansion_trace_fallback()
&& trace.is_some_and(|trace| has_matching_trace(trace, candidates)))
}
fn has_matching_attribute(attrs: &[Attribute], candidates: &[&[&str]]) -> bool {
attrs
.iter()
.any(|attribute| path_matches_candidates(attribute.path(), candidates))
}
fn has_matching_trace(trace: &ExpansionTrace, candidates: &[&[&str]]) -> bool {
trace
.frames()
.iter()
.any(|frame| path_matches_candidates(frame, candidates))
}
fn path_matches_candidates(path: &AttributePath, candidates: &[&[&str]]) -> bool {
candidates
.iter()
.any(|candidate| path.matches(candidate.iter().copied()))
}