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// }