use crate::attributes::{
Attribute, AttributePath, has_test_like_attribute, has_test_like_attribute_with,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ContextKind {
Function,
Impl,
Module,
Block,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContextEntry {
name: String,
kind: ContextKind,
attributes: Vec<Attribute>,
}
impl ContextEntry {
#[must_use]
pub fn new(name: impl Into<String>, kind: ContextKind, attributes: Vec<Attribute>) -> Self {
Self {
name: name.into(),
kind,
attributes,
}
}
#[must_use]
pub fn function(name: impl Into<String>, attributes: Vec<Attribute>) -> Self {
Self::new(name, ContextKind::Function, attributes)
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn kind(&self) -> &ContextKind {
&self.kind
}
#[must_use]
pub fn attributes(&self) -> &[Attribute] {
&self.attributes
}
#[must_use]
pub fn attributes_mut(&mut self) -> &mut Vec<Attribute> {
&mut self.attributes
}
pub fn push_attribute(&mut self, attribute: Attribute) {
self.attributes.push(attribute);
}
}
impl ContextKind {
#[must_use]
pub const fn matches_function(&self) -> bool {
matches!(self, Self::Function)
}
}
#[must_use]
pub fn is_test_fn(attrs: &[Attribute]) -> bool {
has_test_like_attribute(attrs)
}
#[must_use]
pub fn is_test_fn_with(attrs: &[Attribute], additional: &[AttributePath]) -> bool {
has_test_like_attribute_with(attrs, additional)
}
#[must_use]
pub fn in_test_like_context(stack: &[ContextEntry]) -> bool {
in_test_like_context_with(stack, &[])
}
#[must_use]
pub fn in_test_like_context_with(stack: &[ContextEntry], additional: &[AttributePath]) -> bool {
stack
.iter()
.any(|entry| has_test_like_attribute_with(entry.attributes(), additional))
}
#[must_use]
pub fn is_in_main_fn(stack: &[ContextEntry]) -> bool {
stack
.iter()
.rev()
.any(|entry| entry.kind.matches_function() && entry.name() == "main")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attributes::{Attribute, AttributeKind, AttributePath};
use rstest::rstest;
fn test_attribute() -> Attribute {
Attribute::new(AttributePath::from("test"), AttributeKind::Outer)
}
#[rstest]
#[case::plain(Vec::new(), false)]
#[case::rstest(vec![test_attribute()], true)]
fn detects_test_functions(#[case] attrs: Vec<Attribute>, #[case] expected: bool) {
assert_eq!(is_test_fn(&attrs), expected);
}
#[rstest]
fn context_detection() {
let mut entry = ContextEntry::function("demo", Vec::new());
entry.push_attribute(test_attribute());
assert!(in_test_like_context(&[entry]));
}
#[rstest]
fn identifies_main() {
let stack = vec![ContextEntry::function("main", Vec::new())];
assert!(is_in_main_fn(&stack));
}
#[rstest]
fn rejects_non_main() {
let stack = vec![ContextEntry::function("helper", Vec::new())];
assert!(!is_in_main_fn(&stack));
}
#[rstest]
fn honours_additional_attributes() {
let additional = vec![AttributePath::from("custom::test")];
let attrs = vec![Attribute::new(
AttributePath::from("custom::test"),
AttributeKind::Outer,
)];
assert!(is_test_fn_with(&attrs, additional.as_slice()));
let entry = ContextEntry::function("demo", attrs);
assert!(in_test_like_context_with(&[entry], additional.as_slice()));
}
}