Skip to content

Commit fb47ccc

Browse files
committed
macros: custom package name for #[tokio::main] and #[tokio::test]
This also enables `#[crate::test(package = "crate")]` in unit tests. Sees rust-lang/cargo#5653. Fixes tokio-rs#2312.
1 parent 252b0fa commit fb47ccc

File tree

5 files changed

+169
-34
lines changed

5 files changed

+169
-34
lines changed

tests-build/tests/fail/macros_invalid_input.rs

+9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ async fn test_worker_threads_not_int() {}
3333
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
3434
async fn test_worker_threads_and_current_thread() {}
3535

36+
#[tokio::test(package = 456)]
37+
async fn test_package_not_ident_int() {}
38+
39+
#[tokio::test(package = "456")]
40+
async fn test_package_not_ident_invalid() {}
41+
42+
#[tokio::test(package = "abc::edf")]
43+
async fn test_package_not_ident_path() {}
44+
3645
#[tokio::test]
3746
#[test]
3847
async fn test_has_second_test_attr() {}

tests-build/tests/fail/macros_invalid_input.stderr

+25-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error: the `async` keyword is missing from the function declaration
44
4 | fn main_is_not_async() {}
55
| ^^
66

7-
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
7+
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `package`
88
--> $DIR/macros_invalid_input.rs:6:15
99
|
1010
6 | #[tokio::main(foo)]
@@ -22,13 +22,13 @@ error: the `async` keyword is missing from the function declaration
2222
13 | fn test_is_not_async() {}
2323
| ^^
2424

25-
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
25+
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `package`
2626
--> $DIR/macros_invalid_input.rs:15:15
2727
|
2828
15 | #[tokio::test(foo)]
2929
| ^^^
3030

31-
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
31+
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `package`
3232
--> $DIR/macros_invalid_input.rs:18:15
3333
|
3434
18 | #[tokio::test(foo = 123)]
@@ -64,16 +64,34 @@ error: The `worker_threads` option requires the `multi_thread` runtime flavor. U
6464
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
6565
| ^
6666

67+
error: Failed to parse value of `package` as ident.
68+
--> $DIR/macros_invalid_input.rs:36:25
69+
|
70+
36 | #[tokio::test(package = 456)]
71+
| ^^^
72+
73+
error: Failed to parse value of `package` as ident: "456"
74+
--> $DIR/macros_invalid_input.rs:39:25
75+
|
76+
39 | #[tokio::test(package = "456")]
77+
| ^^^^^
78+
79+
error: Failed to parse value of `package` as ident: "abc::edf"
80+
--> $DIR/macros_invalid_input.rs:42:25
81+
|
82+
42 | #[tokio::test(package = "abc::edf")]
83+
| ^^^^^^^^^^
84+
6785
error: second test attribute is supplied
68-
--> $DIR/macros_invalid_input.rs:37:1
86+
--> $DIR/macros_invalid_input.rs:46:1
6987
|
70-
37 | #[test]
88+
46 | #[test]
7189
| ^^^^^^^
7290

7391
error: duplicated attribute
74-
--> $DIR/macros_invalid_input.rs:37:1
92+
--> $DIR/macros_invalid_input.rs:46:1
7593
|
76-
37 | #[test]
94+
46 | #[test]
7795
| ^^^^^^^
7896
|
7997
= note: `-D duplicate-macro-attributes` implied by `-D warnings`

tokio-macros/src/entry.rs

+51-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use proc_macro::TokenStream;
2-
use proc_macro2::Span;
2+
use proc_macro2::{Ident, Span};
33
use quote::{quote, quote_spanned, ToTokens};
44
use syn::parse::Parser;
55

@@ -29,13 +29,15 @@ struct FinalConfig {
2929
flavor: RuntimeFlavor,
3030
worker_threads: Option<usize>,
3131
start_paused: Option<bool>,
32+
package_name: Option<String>,
3233
}
3334

3435
/// Config used in case of the attribute not being able to build a valid config
3536
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
3637
flavor: RuntimeFlavor::CurrentThread,
3738
worker_threads: None,
3839
start_paused: None,
40+
package_name: None,
3941
};
4042

4143
struct Configuration {
@@ -45,6 +47,7 @@ struct Configuration {
4547
worker_threads: Option<(usize, Span)>,
4648
start_paused: Option<(bool, Span)>,
4749
is_test: bool,
50+
package_name: Option<String>,
4851
}
4952

5053
impl Configuration {
@@ -59,6 +62,7 @@ impl Configuration {
5962
worker_threads: None,
6063
start_paused: None,
6164
is_test,
65+
package_name: None,
6266
}
6367
}
6468

@@ -104,6 +108,15 @@ impl Configuration {
104108
Ok(())
105109
}
106110

111+
fn set_package_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
112+
if self.package_name.is_some() {
113+
return Err(syn::Error::new(span, "`package` set multiple times."));
114+
}
115+
let name_ident = parse_ident(name, span, "package")?;
116+
self.package_name = Some(name_ident.to_string());
117+
Ok(())
118+
}
119+
107120
fn macro_name(&self) -> &'static str {
108121
if self.is_test {
109122
"tokio::test"
@@ -151,6 +164,7 @@ impl Configuration {
151164
};
152165

153166
Ok(FinalConfig {
167+
package_name: self.package_name.clone(),
154168
flavor,
155169
worker_threads,
156170
start_paused,
@@ -185,6 +199,27 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
185199
}
186200
}
187201

202+
fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
203+
match lit {
204+
syn::Lit::Str(s) => {
205+
let err = syn::Error::new(
206+
span,
207+
format!(
208+
"Failed to parse value of `{}` as ident: \"{}\"",
209+
field,
210+
s.value()
211+
),
212+
);
213+
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
214+
path.get_ident().cloned().ok_or(err)
215+
}
216+
_ => Err(syn::Error::new(
217+
span,
218+
format!("Failed to parse value of `{}` as ident.", field),
219+
)),
220+
}
221+
}
222+
188223
fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> {
189224
match bool {
190225
syn::Lit::Bool(b) => Ok(b.value),
@@ -243,9 +278,15 @@ fn build_config(
243278
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
244279
return Err(syn::Error::new_spanned(namevalue, msg));
245280
}
281+
"package" => {
282+
config.set_package_name(
283+
namevalue.lit.clone(),
284+
syn::spanned::Spanned::span(&namevalue.lit),
285+
)?;
286+
}
246287
name => {
247288
let msg = format!(
248-
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`",
289+
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `package`",
249290
name,
250291
);
251292
return Err(syn::Error::new_spanned(namevalue, msg));
@@ -275,7 +316,7 @@ fn build_config(
275316
format!("The `{}` attribute requires an argument.", name)
276317
}
277318
name => {
278-
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", name)
319+
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `package`", name)
279320
}
280321
};
281322
return Err(syn::Error::new_spanned(path, msg));
@@ -313,12 +354,17 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
313354
(start, end)
314355
};
315356

357+
let package_name = config
358+
.package_name
359+
.map(|name| Ident::new(&name, last_stmt_start_span))
360+
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span));
361+
316362
let mut rt = match config.flavor {
317363
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
318-
tokio::runtime::Builder::new_current_thread()
364+
#package_name::runtime::Builder::new_current_thread()
319365
},
320366
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
321-
tokio::runtime::Builder::new_multi_thread()
367+
#package_name::runtime::Builder::new_multi_thread()
322368
},
323369
};
324370
if let Some(v) = config.worker_threads {

tokio-macros/src/lib.rs

+59-22
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,32 @@ use proc_macro::TokenStream;
168168
///
169169
/// Note that `start_paused` requires the `test-util` feature to be enabled.
170170
///
171-
/// ### NOTE:
171+
/// ### Rename package
172172
///
173-
/// If you rename the Tokio crate in your dependencies this macro will not work.
174-
/// If you must rename the current version of Tokio because you're also using an
175-
/// older version of Tokio, you _must_ make the current version of Tokio
176-
/// available as `tokio` in the module where this macro is expanded.
173+
/// ```rust
174+
/// use tokio as tokio1;
175+
///
176+
/// #[tokio1::main(package = "tokio1")]
177+
/// async fn main() {
178+
/// println!("Hello world");
179+
/// }
180+
/// ```
181+
///
182+
/// Equivalent code not using `#[tokio::main]`
183+
///
184+
/// ```rust
185+
/// use tokio as tokio1;
186+
///
187+
/// fn main() {
188+
/// tokio1::runtime::Builder::new_multi_thread()
189+
/// .enable_all()
190+
/// .build()
191+
/// .unwrap()
192+
/// .block_on(async {
193+
/// println!("Hello world");
194+
/// })
195+
/// }
196+
/// ```
177197
#[proc_macro_attribute]
178198
#[cfg(not(test))] // Work around for rust-lang/rust#62127
179199
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
@@ -213,12 +233,32 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
213233
/// }
214234
/// ```
215235
///
216-
/// ### NOTE:
236+
/// ### Rename package
237+
///
238+
/// ```rust
239+
/// use tokio as tokio1;
240+
///
241+
/// #[tokio1::main(package = "tokio1")]
242+
/// async fn main() {
243+
/// println!("Hello world");
244+
/// }
245+
/// ```
246+
///
247+
/// Equivalent code not using `#[tokio::main]`
248+
///
249+
/// ```rust
250+
/// use tokio as tokio1;
217251
///
218-
/// If you rename the Tokio crate in your dependencies this macro will not work.
219-
/// If you must rename the current version of Tokio because you're also using an
220-
/// older version of Tokio, you _must_ make the current version of Tokio
221-
/// available as `tokio` in the module where this macro is expanded.
252+
/// fn main() {
253+
/// tokio1::runtime::Builder::new_multi_thread()
254+
/// .enable_all()
255+
/// .build()
256+
/// .unwrap()
257+
/// .block_on(async {
258+
/// println!("Hello world");
259+
/// })
260+
/// }
261+
/// ```
222262
#[proc_macro_attribute]
223263
#[cfg(not(test))] // Work around for rust-lang/rust#62127
224264
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
@@ -260,12 +300,16 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
260300
///
261301
/// Note that `start_paused` requires the `test-util` feature to be enabled.
262302
///
263-
/// ### NOTE:
303+
/// ### Rename package
304+
///
305+
/// ```rust
306+
/// use tokio as tokio1;
264307
///
265-
/// If you rename the Tokio crate in your dependencies this macro will not work.
266-
/// If you must rename the current version of Tokio because you're also using an
267-
/// older version of Tokio, you _must_ make the current version of Tokio
268-
/// available as `tokio` in the module where this macro is expanded.
308+
/// #[tokio1::test(package = "tokio1")]
309+
/// async fn my_test() {
310+
/// println!("Hello world");
311+
/// }
312+
/// ```
269313
#[proc_macro_attribute]
270314
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
271315
entry::test(args, item, true)
@@ -281,13 +325,6 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
281325
/// assert!(true);
282326
/// }
283327
/// ```
284-
///
285-
/// ### NOTE:
286-
///
287-
/// If you rename the Tokio crate in your dependencies this macro will not work.
288-
/// If you must rename the current version of Tokio because you're also using an
289-
/// older version of Tokio, you _must_ make the current version of Tokio
290-
/// available as `tokio` in the module where this macro is expanded.
291328
#[proc_macro_attribute]
292329
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
293330
entry::test(args, item, false)

tokio/tests/macros_rename_test.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![cfg(feature = "full")]
2+
3+
use std as tokio;
4+
5+
use ::tokio as tokio1;
6+
7+
async fn compute() -> usize {
8+
let join = tokio1::spawn(async { tokio::mem::size_of::<u8>() });
9+
join.await.unwrap()
10+
}
11+
12+
#[tokio1::main(package = "tokio1")]
13+
async fn compute_main() -> usize {
14+
compute().await
15+
}
16+
17+
#[test]
18+
fn package_rename_main() {
19+
assert_eq!(1, compute_main());
20+
}
21+
22+
#[tokio1::test(package = "tokio1")]
23+
async fn package_rename_test() {
24+
assert_eq!(1, compute().await);
25+
}

0 commit comments

Comments
 (0)