Skip to content

Commit

Permalink
Optimize runtime of #[cargo_test_macro]
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
alexcrichton committed Jul 19, 2019
1 parent 2b21fa6 commit 8887b67
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 22 deletions.
4 changes: 0 additions & 4 deletions crates/cargo-test-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
69 changes: 51 additions & 18 deletions crates/cargo-test-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 8887b67

Please sign in to comment.