safina_async_test_core/
lib.rs

1//! # ARCHIVED ARCHIVED ARCHIVED
2//! This crate is archived and will not be updated.
3//!
4//! The code is now in the [`safina-macros`](https://crates.io/crates/safina-macros) crate.
5//!
6//! ----
7//!
8//! # safina-async-test-core
9//!
10//! Procedural macro for the [`safina-async-test`](https://crates.io/crates/safina-async-test)
11//! package
12//!
13//! ## Changelog
14//! - V0.1.4 - Update docs.
15//! - v0.1.3 - Use `safina-executor` v0.2.0.
16//! - v0.1.2
17//!   - Depend on `safe_proc_macro2` and `safe_quote`.
18//!   - Work properly on test functions with annotations.
19//! - v0.1.1 - Fix paths.
20//! - v0.1.0 - Initial version
21//!
22//! # Release Process
23//! 1. Edit `Cargo.toml` and bump version number.
24//! 1. Run `./release.sh`
25#![forbid(unsafe_code)]
26
27use safe_proc_macro2::{Ident, TokenStream, TokenTree};
28use safe_quote::quote_spanned;
29
30#[proc_macro_attribute]
31pub fn async_test(
32    attr: proc_macro::TokenStream,
33    item: proc_macro::TokenStream,
34) -> proc_macro::TokenStream {
35    let mut result = proc_macro::TokenStream::from(impl_async_test(
36        safe_proc_macro2::TokenStream::from(attr),
37        safe_proc_macro2::TokenStream::from(item.clone()),
38    ));
39    result.extend(item);
40
41    // let mut token_stream_string = String::new();
42    // for tree in result {
43    //     token_stream_string.push_str(format!("tree: {:?}\n", tree).as_str());
44    // }
45    // panic!("TokenStream:\n{}", token_stream_string);
46
47    result
48}
49
50fn impl_async_test(attr: TokenStream, item: TokenStream) -> TokenStream {
51    if let Some(tree) = attr.into_iter().next() {
52        return quote_spanned!(tree.span()=>compile_error!("parameters not allowed"););
53    }
54    // Ident { ident: "async", span: #0 bytes(50..55) }
55    // Ident { ident: "fn", span: #0 bytes(56..58) }
56    // Ident { ident: "should_run_async_fn", span: #0 bytes(59..78) }
57    // Group { delimiter: Parenthesis, stream: TokenStream [], span: ...
58    // Group { delimiter: Brace, stream: TokenStream [Ident { ident: "println",...
59    let mut trees = item.into_iter();
60    let mut first;
61    loop {
62        first = trees.next();
63        match first {
64            Some(TokenTree::Ident(ref ident_async)) if *ident_async == "async" => break,
65            None => break,
66            _ => {}
67        }
68    }
69    if let (
70        Some(TokenTree::Ident(ref ident_async)),
71        Some(TokenTree::Ident(ref ident_fn)),
72        Some(TokenTree::Ident(ref ident_name)),
73    ) = (&first, trees.next(), trees.next())
74    {
75        if *ident_async == "async" && *ident_fn == "fn" {
76            let async_name = ident_name.to_string() + "_";
77            let ident_async_name = Ident::new(&async_name, ident_name.span());
78            return quote_spanned!(ident_name.span()=>
79                #[test]
80                fn #ident_async_name () {
81                    safina_async_test::start_timer_thread();
82                    safina_async_test::Executor::new(2, 1).unwrap().block_on( #ident_name ())
83                }
84            );
85        }
86    }
87    if let Some(tree) = first {
88        return quote_spanned!(tree.span()=>compile_error!("expected async fn"););
89    }
90    panic!("expected async fn");
91}
92
93// The version below has two drawbacks:
94// 1. IDEs don't know that the stuff inside `async_test! { async fn test1() { ... } }` is a
95//    function, so they don't provide formatting, auto-complete, or other useful editor functions.
96//    This is a fatal flaw.
97// 2. It puts the test in a module with the same name as the test function,
98//    and a test function named `async_test`.
99//    So for `async_test! { async fn test1() {...} }` below, it makes
100//    `mod test1 { #[test] fn async_test() {...} }`.
101//    This makes IDE test output more verbose.  Each test appears as its own category.
102//
103// macro_rules! async_test {
104//     (async fn $name:ident () $body:block) => {
105//         async fn $name() $body
106//
107//         mod $name {
108//             #[test]
109//             fn async_test () {
110//                 safina_executor::block_on(super::$name())
111//             }
112//         }
113//     }
114// }
115//
116// async_test! {
117// async fn test1() {
118//     println!("test1");
119// }
120// }
121
122// The version below requires the user to keep the two names in sync.
123// Also, the error messages are not great.
124//
125// macro_rules! async_test {
126//     ($name:ident) => {
127//         mod $name {
128//             #[test]
129//             fn async_test () {
130//                 safina_executor::block_on(super::$name())
131//             }
132//         }
133//     }
134// }
135//
136// async_test!(test1);
137// async fn test1() {
138//     println!("test1");
139// }
140//
141// async_test!(test1); // error[E0428]: the name `test1` is defined multiple times
142// async fn test2() {
143//     println!("test2");
144// }
145//
146// async_test!(test3); // error[E0423]: expected function, found module `super::test3`
147// async fn test4() {
148//     println!("test4");
149// }