[go: up one dir, main page]

rstest 0.4.1

Rust fixture based test framework. It use procedural macro to implement fixtures and table based tests.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{Ident, parse::{Parse, ParseStream, Result}, Token, Expr};

use crate::refident::RefIdent;

use super::{Attributes, Fixture, parse_vector_trailing_till_double_comma};

#[derive(Default)]
pub(crate) struct MatrixInfo {
    pub(crate) args: MatrixData,
    pub(crate) attributes: Attributes,
}

impl Parse for MatrixInfo {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(
            MatrixInfo {
                args: parse_vector_trailing_till_double_comma::<_, Token![,]>(input)
                    .map(|items| MatrixData { items } )?,
                attributes: input.parse::<Token![::]>()
                    .or_else(|_| Ok(Default::default()))
                    .and_then(|_| input.parse())?,
            }
        )
    }
}

#[derive(Default)]
pub(crate) struct MatrixData { pub(crate) items: Vec<MatrixItem> }

impl MatrixData {
    pub(crate) fn list_values(&self) -> impl Iterator<Item=&ValueList> {
        self.items.iter().filter_map(|mv|
            match mv {
                MatrixItem::ValueList(ref value_list) => Some(value_list),
                _ => None
            }
        )
    }

    pub(crate) fn fixtures(&self) -> impl Iterator<Item=&Fixture> {
        self.items.iter().filter_map(|mv|
            match mv {
                MatrixItem::Fixture(ref fixture) => Some(fixture),
                _ => None
            }
        )
    }
}

pub(crate) enum MatrixItem {
    ValueList(ValueList),
    Fixture(Fixture),
}

impl Parse for MatrixItem {
    fn parse(input: ParseStream) -> Result<Self> {
        if input.peek2(Token![=>]) {
            input.parse::<ValueList>().map(Self::from)
        } else if input.fork().parse::<Fixture>().is_ok() {
            input.parse::<Fixture>().map(Self::from)
        } else {
            Err(syn::Error::new(Span::call_site(), "Cannot parse matrix info"))
        }
    }
}

impl From<ValueList> for MatrixItem {
    fn from(value_list: ValueList) -> Self {
        MatrixItem::ValueList(value_list)
    }
}

impl From<Fixture> for MatrixItem {
    fn from(fixture: Fixture) -> Self {
        MatrixItem::Fixture(fixture)
    }
}

impl RefIdent for MatrixItem {
    fn ident(&self) -> &Ident {
        match self {
            MatrixItem::ValueList(ref values) => values.ident(),
            MatrixItem::Fixture(ref fixture) => fixture.ident(),
        }
    }
}

impl ToTokens for MatrixItem {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.ident().to_tokens(tokens)
    }
}

pub(crate) struct ValueList {
    pub(crate) arg: Ident,
    pub(crate) values: Vec<Expr>,
}

impl Parse for ValueList {
    fn parse(input: ParseStream) -> Result<Self> {
        let arg = input.parse()?;
        let _to: Token![=>] = input.parse()?;
        let content;
        let paren = syn::bracketed!(content in input);
        let values = content
            .parse_terminated::<_, Token![,]>(Parse::parse)?
            .into_iter()
            .collect();

        let ret = Self {
            arg,
            values,
        };
        if ret.values.len() == 0 {
            Err(syn::Error::new(paren.span, "Values list should not be empty"))
        } else {
            Ok(ret)
        }
    }
}

impl RefIdent for ValueList {
    fn ident(&self) -> &Ident {
        &self.arg
    }
}

impl ToTokens for ValueList {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.arg.to_tokens(tokens)
    }
}

#[cfg(test)]
mod should {
    use crate::test::{*, assert_eq};

    use super::*;

    mod parse_values_list {
        use super::*;
        use super::assert_eq;

        fn parse_values_list<S: AsRef<str>>(values_list: S) -> ValueList {
            parse_meta(values_list)
        }

        #[test]
        fn some_literals() {
            let literals = literal_expressions_str();
            let name = "argument";

            let values_list = parse_values_list(
                format!(r#"{} => [{}]"#, name,
                        literals
                            .iter()
                            .map(ToString::to_string)
                            .collect::<Vec<String>>()
                            .join(", "))
            );

            assert_eq!(name, &values_list.arg.to_string());
            assert_eq!(values_list.args(), to_args!(literals));
        }

        #[test]
        fn raw_code() {
            let values_list = parse_values_list(r#"no_mater => [vec![1,2,3]]"#);

            assert_eq!(values_list.args(), to_args!(["vec![1, 2, 3]"]));
        }

        #[test]
        #[should_panic]
        fn raw_code_with_parsing_error() {
            parse_values_list(r#"other => [some:<>(1,2,3)]"#);
        }

        #[test]
        #[should_panic(expected = r#"expected square brackets"#)]
        fn forget_brackets() {
            parse_values_list(r#"other => 42"#);
        }
    }

    mod parse_matrix_info {
        use itertools::Itertools;

        use crate::parse::Attribute;

        use super::*;
        use super::assert_eq;

        fn parse_matrix_info<S: AsRef<str>>(matrix_info: S) -> MatrixInfo {
            parse_meta(matrix_info)
        }

        #[test]
        fn happy_path() {
            let info = parse_matrix_info(r#"
            expected => [12, 34 * 2],
            input => [format!("aa_{}", 2), "other"],
        "#);

            let value_ranges = info.args.list_values().collect_vec();
            assert_eq!(2, value_ranges.len());
            assert_eq!(to_args!(["12", "34 * 2"]), value_ranges[0].args());
            assert_eq!(to_args!([r#"format!("aa_{}", 2)"#, r#""other""#]), value_ranges[1].args());
            assert_eq!(info.attributes, Default::default());
        }

        #[test]
        fn should_parse_attributes_too() {
            let info = parse_matrix_info(r#"
                                            a => [12, 24, 42]
                                            ::trace
                                        "#);

            assert_eq!(Attributes { attributes: vec![Attribute::attr("trace")] },
                       info.attributes);
        }

        #[test]
        fn should_parse_injected_fixtures_too() {
            let info = parse_matrix_info(
                r#"
                a => [12, 24, 42],
                fixture_1(42, "foo"),
                fixture_2("bar")
                "#);

            let fixtures = info.args.fixtures().cloned().collect_vec();

            assert_eq!(vec![fixture("fixture_1", vec!["42", r#""foo""#]),
                            fixture("fixture_2", vec![r#""bar""#])],
                       fixtures);
        }

        #[test]
        #[should_panic(expected = "should not be empty")]
        fn should_not_compile_if_empty_expression_slice() {
            parse_matrix_info(r#"
                invalid => []
                "#);
        }
    }
}