proc-macro-assertions 0.1.5

Easily create asserts on proc macro inputs
Documentation
use std::collections::VecDeque;

use proc_macro2::Span;
use syn::Ident;

use super::IdentGenerator;

/// A mock ident generator which can be used to generate consistent idents
/// for unit testing.
#[derive(Default, Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct MockIdentGenerator {
    idents: VecDeque<&'static str>,
    calls: usize,
}

impl MockIdentGenerator {
    /// Create a new mock ident generator
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Reset the mock ident generator to a empty state without reallocating.
    #[allow(dead_code)]
    pub fn reset(&mut self) {
        self.idents.clear();
        self.calls = 0;
    }

    /// Push idents which are returned in order while generating idents using this ident generator
    pub fn push_ident(&mut self, ident: &'static str) {
        self.idents.push_back(ident);
    }

    /// Ensure the ident generator was called at least once
    #[allow(dead_code)]
    #[must_use]
    pub const fn was_called(&self) -> bool {
        self.calls > 0
    }

    /// Ensure that the ident generator was never called
    #[must_use]
    pub const fn was_not_called(&self) -> bool {
        self.calls == 0
    }

    /// Ensure that the ident generator was called exactly once
    #[allow(dead_code)]
    #[must_use]
    pub const fn was_called_once(&self) -> bool {
        self.calls == 1
    }

    /// Ensure that the ident generator was called ecactly `times` times.
    #[allow(dead_code)]
    #[must_use]
    pub const fn was_called_n_times(&self, times: usize) -> bool {
        self.calls == times
    }

    /// Ensure that all idents which were pushed using [`MockIdentGenerator::push_ident`] were consumed
    #[must_use]
    pub fn has_no_idents_remaining(&self) -> bool {
        self.idents.is_empty()
    }
}

impl IdentGenerator for MockIdentGenerator {
    fn generate(&mut self, prefix: Option<&'static str>, span: Span) -> Ident {
        let mock_str = self
            .idents
            .pop_front()
            .expect("should have added enough mock idents for test case");

        self.calls += 1;

        prefix.map_or_else(
            || Ident::new(mock_str, span),
            |ident| Ident::new(format!("{ident}_{mock_str}").as_str(), span),
        )
    }
}

#[cfg(test)]
mod test {
    use proc_macro2::Span;
    use syn::Ident;

    use crate::ident_generator::IdentGenerator;

    use super::MockIdentGenerator;

    #[test]
    fn ident() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        mock_ident_gen.push_ident("mock_1");

        assert_eq!(
            mock_ident_gen.ident(),
            Ident::new("mock_1", Span::call_site())
        );
    }

    #[test]
    fn prefixed() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        mock_ident_gen.push_ident("mock_1");

        assert_eq!(
            mock_ident_gen.prefixed("prefix"),
            Ident::new("prefix_mock_1", Span::call_site())
        );
    }

    #[test]
    fn spanned() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        mock_ident_gen.push_ident("mock_1");

        assert_eq!(
            mock_ident_gen.spanned(Span::mixed_site()),
            Ident::new("mock_1", Span::mixed_site())
        );
    }

    #[test]
    fn prefixed_spanned() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        mock_ident_gen.push_ident("mock_1");

        assert_eq!(
            mock_ident_gen.generate(Some("prefix"), Span::mixed_site()),
            Ident::new("prefix_mock_1", Span::mixed_site())
        );
    }

    #[test]
    #[should_panic]
    fn panics() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        let _ = mock_ident_gen.ident();
    }

    #[test]
    fn call_count_not_called() {
        let mock_ident_gen = MockIdentGenerator::new();
        assert!(mock_ident_gen.was_not_called());
        assert!(!mock_ident_gen.was_called());
    }

    #[test]
    fn call_count_called() {
        let mut mock_ident_gen = MockIdentGenerator::new();
        mock_ident_gen.push_ident("mock_1");
        let _ = mock_ident_gen.ident();
        assert!(!mock_ident_gen.was_not_called());
        assert!(mock_ident_gen.was_called());
    }

    #[test]
    fn call_count_called_n_times() {
        let mut mock_ident_gen = MockIdentGenerator::new();
        mock_ident_gen.push_ident("mock_1");
        mock_ident_gen.push_ident("mock_2");
        mock_ident_gen.push_ident("mock_3");
        mock_ident_gen.push_ident("mock_4");
        mock_ident_gen.push_ident("mock_5");
        assert!(mock_ident_gen.was_not_called());
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.was_called_once());
        assert!(mock_ident_gen.was_called_n_times(1));
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.was_called_n_times(2));
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.was_called_n_times(3));
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.was_called_n_times(4));
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.was_called_n_times(5));
    }

    #[test]
    fn has_no_idents_remaining() {
        let mut mock_ident_gen = MockIdentGenerator::new();
        mock_ident_gen.push_ident("mock_1");
        assert!(!mock_ident_gen.has_no_idents_remaining());
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.has_no_idents_remaining());
        mock_ident_gen.push_ident("mock_1");
        assert!(!mock_ident_gen.has_no_idents_remaining());
        let _ = mock_ident_gen.ident();
        assert!(mock_ident_gen.has_no_idents_remaining());
    }
}