iceoryx2_bb_conformance_test_macros/
lib.rs

1// Copyright (c) 2025 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13#![warn(clippy::alloc_instead_of_core)]
14#![warn(clippy::std_instead_of_alloc)]
15#![warn(clippy::std_instead_of_core)]
16
17//! Attribute macro for defining a module containing conformance tests.
18//!
19//! This macro processes a module, collects all functions marked with
20//! `#[conformance_test]`, and generates a new declarative macro named after the module.
21//! The generated macro, when invoked, creates a test module with a test for each
22//! conformance function, instantiated for each provided type.
23//!
24//! Have a look at the `How It Works` section of the how-to-write-conformance-tests.md
25//! document for more details.
26
27use proc_macro::TokenStream;
28use quote::quote;
29use syn::{parse_macro_input, ItemMod};
30
31/// Attribute macro for marking functions as conformance tests.
32///
33/// Functions marked with this attribute will be included in the generated
34/// test suite when the module is processed by `conformance_test_module`.
35#[proc_macro_attribute]
36pub fn conformance_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
37    item
38}
39
40/// Attribute macro for defining a conformance test module.
41///
42/// This macro scans a module for functions annotated with `#[conformance_test]` and generates a declarative
43/// macro that, when invoked, will run all the conformance tests for the specified System Under Test (SUT)
44/// types.
45///
46/// Have a look at the `How It Works` section of the how-to-write-conformance-tests.md
47/// document for more details.
48#[proc_macro_attribute]
49pub fn conformance_test_module(_attr: TokenStream, item: TokenStream) -> TokenStream {
50    let input = parse_macro_input!(item as ItemMod);
51    let mod_ident = &input.ident;
52    let macro_name = mod_ident;
53
54    // collect all functions with #[conformance_test] attribute
55    let conformance_test_fns = collect_conformance_test_functions(&input, mod_ident);
56
57    // generate and append the declarative macro to the current module
58    let output = quote! {
59        #input
60
61        #[macro_export]
62        macro_rules! #macro_name {
63            ($module_path:path, $($sut_type:ty),+) => {
64                mod #mod_ident {
65                    use $module_path::*;
66                    #(#conformance_test_fns)*
67                }
68            };
69        }
70    };
71
72    output.into()
73}
74
75/// Collects all functions marked with `#[conformance_test]` in a module.
76///
77/// For each such function, a test function is generated that calls the original
78/// function, instantiated for each type provided to the generated macro.
79///
80/// # Arguments
81///
82/// * `module` - The module to scan for conformance test functions.
83/// * `mod_ident` - The identifier of the module.
84///
85/// # Returns
86///
87/// A vector of token streams, each representing a generated test function.
88fn collect_conformance_test_functions(
89    module: &ItemMod,
90    mod_ident: &syn::Ident,
91) -> Vec<proc_macro2::TokenStream> {
92    let mut conformance_test_fns = Vec::new();
93
94    if let Some((_brace, items)) = &module.content {
95        for item in items {
96            if let syn::Item::Fn(func) = item {
97                let fn_ident = &func.sig.ident;
98                let fn_attrs = &func.attrs;
99                let fn_return = &func.sig.output;
100
101                // check if the function has #[conformance_test]
102                if fn_attrs
103                    .iter()
104                    .any(|attr| attr.path().is_ident("conformance_test"))
105                {
106                    // collect all attributes except #[conformance_test]
107                    let test_attrs = fn_attrs
108                        .iter()
109                        .filter(|attr| !attr.path().is_ident("conformance_test"))
110                        .collect::<Vec<_>>();
111
112                    // generate the new test function
113                    conformance_test_fns.push(quote! {
114                        #(#test_attrs)*
115                        #[test]
116                        fn #fn_ident() #fn_return {
117                            #mod_ident::#fn_ident::<$($sut_type),+>()
118                        }
119                    });
120                }
121            }
122        }
123    }
124
125    conformance_test_fns
126}