mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Optimize runtime of #[cargo_test_macro]
I've noticed recently that the incremental compile time for our test suite has felt like it's increased quite a bit. I think one reason is that everything has to go through `#[cargo_test_macro]` unconditionally on all incremental builds, and wow do we have a lot of tests being pumped through that macro. Instrumenting the macro a little bit shows that we spend nearly 2.5 seconds on each compilation simply executing this macro (note that it's in debug mode as well, not release since we typically don't execute tests in release mode. This commit instead drops the usage of `syn` and `quote` in favor of a "raw procedural macro" which is much more optimized for just our use case, even in debug mode. This drops the collective time spent in the macro to 0.2 seconds, even in debug mode!
This commit is contained in:
parent
2b21fa6836
commit
8887b67ec5
@ -11,7 +11,3 @@ description = "Helper proc-macro for Cargo's testsuite."
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "0.6"
|
||||
syn = { version = "0.15", features = ["full"] }
|
||||
|
@ -1,26 +1,59 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse::Parser, *};
|
||||
use proc_macro::*;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn cargo_test(
|
||||
_attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let mut fn_def = parse_macro_input!(item as ItemFn);
|
||||
pub fn cargo_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let span = Span::call_site();
|
||||
let mut ret = TokenStream::new();
|
||||
ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
|
||||
let test = TokenTree::from(Ident::new("test", span));
|
||||
ret.extend(Some(TokenTree::from(Group::new(
|
||||
Delimiter::Bracket,
|
||||
test.into(),
|
||||
))));
|
||||
|
||||
let attr = quote! {
|
||||
#[test]
|
||||
};
|
||||
fn_def
|
||||
.attrs
|
||||
.extend(Attribute::parse_outer.parse2(attr).unwrap());
|
||||
for token in item {
|
||||
let group = match token {
|
||||
TokenTree::Group(g) => {
|
||||
if g.delimiter() == Delimiter::Brace {
|
||||
g
|
||||
} else {
|
||||
ret.extend(Some(TokenTree::Group(g)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
other => {
|
||||
ret.extend(Some(other));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let stmt = quote! {
|
||||
let _test_guard = crate::support::paths::init_root();
|
||||
};
|
||||
fn_def.block.stmts.insert(0, parse2(stmt).unwrap());
|
||||
let mut new_body = vec![
|
||||
TokenTree::from(Ident::new("let", span)),
|
||||
TokenTree::from(Ident::new("_test_guard", span)),
|
||||
TokenTree::from(Punct::new('=', Spacing::Alone)),
|
||||
TokenTree::from(Ident::new("crate", span)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Joint)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Alone)),
|
||||
TokenTree::from(Ident::new("support", span)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Joint)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Alone)),
|
||||
TokenTree::from(Ident::new("paths", span)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Joint)),
|
||||
TokenTree::from(Punct::new(':', Spacing::Alone)),
|
||||
TokenTree::from(Ident::new("init_root", span)),
|
||||
TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new())),
|
||||
TokenTree::from(Punct::new(';', Spacing::Alone)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<TokenStream>();
|
||||
new_body.extend(group.stream());
|
||||
ret.extend(Some(TokenTree::from(Group::new(
|
||||
group.delimiter(),
|
||||
new_body,
|
||||
))));
|
||||
}
|
||||
|
||||
fn_def.into_token_stream().into()
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user