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}