test_harness/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(
3    clippy::dbg_macro,
4    missing_copy_implementations,
5    rustdoc::missing_crate_level_docs,
6    missing_debug_implementations,
7    nonstandard_style,
8    unused_qualifications
9)]
10#![warn(missing_docs, clippy::nursery, clippy::cargo)]
11#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]
12#![doc = include_str!("../README.md")]
13use proc_macro::TokenStream;
14use proc_macro2::TokenStream as TokenStream2;
15use quote::{quote, TokenStreamExt};
16use syn::{
17    parse::{Parse, ParseStream},
18    parse_macro_input,
19    token::Eq,
20    AttrStyle, ExprPath, ItemFn, Path, Token,
21};
22
23struct Args {
24    harness: Option<Path>,
25}
26
27impl Parse for Args {
28    fn parse(input: ParseStream) -> syn::Result<Self> {
29        if input.is_empty() {
30            return Ok(Self { harness: None });
31        }
32
33        let harness_ident: ExprPath = input.parse()?;
34        if !harness_ident.path.is_ident("harness") {
35            Err(input.error(
36                "we only recognize #[test(harness = some::path)], #[test(harness)], and #[test]",
37            ))
38        } else if input.peek(Token![=]) {
39            let Eq { .. } = input.parse()?;
40            let ExprPath { path, .. } = input.parse()?;
41            Ok(Self {
42                harness: Some(path),
43            })
44        } else {
45            Ok(Self {
46                harness: Some(harness_ident.path),
47            })
48        }
49    }
50}
51
52/// currently supports #[test_harness::test(harness = path::to::harness_fn)] and #[test]
53/// see crate-level docs for usage and examples
54#[proc_macro_attribute]
55pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
56    let Args { harness } = parse_macro_input!(args as Args);
57    match harness {
58        Some(harness) => with_harness(harness, input),
59        None => without_harness(input),
60    }
61}
62
63fn without_harness(input: TokenStream) -> TokenStream {
64    let input = TokenStream2::from(input);
65    quote! {
66        #[::core::prelude::v1::test]
67        #input
68    }
69    .into()
70}
71
72fn with_harness(harness: Path, input: TokenStream) -> TokenStream {
73    let ItemFn {
74        attrs,
75        sig,
76        block,
77        vis,
78    } = parse_macro_input!(input as ItemFn);
79
80    let mut outer = TokenStream2::new();
81    outer.append_all(
82        attrs
83            .iter()
84            .filter(|attr| matches!(attr.style, AttrStyle::Outer)),
85    );
86
87    let mut inner = TokenStream2::new();
88    inner.append_all(
89        attrs
90            .iter()
91            .filter(|attr| matches!(attr.style, AttrStyle::Inner(_))),
92    );
93
94    let ident = &sig.ident;
95    let output = if attrs.iter().any(|x| x.meta.path().is_ident("should_panic")) {
96        quote!()
97    } else {
98        quote!(-> impl ::std::process::Termination)
99    };
100
101    quote! {
102        #[::core::prelude::v1::test]
103        #outer
104        #vis fn #ident() #output {
105            #inner
106            #sig #block
107            #harness(#ident)
108        }
109    }
110    .into()
111}