blob: 8436ab00a94188976207a7849d9aefd6a00b8299 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This crate defines attribute proc macros for writing tests with its sibling test-harness crate.
//! Specifically, marking fns which take `test-harness::TestHarness` with the macros in this crate
//! enables them to run with the standard Rust test runner. While `run_singlethreaded_test` is
//! currently the only attribute available, further attributes could be defined for other execution
//! environments.
use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, Error, Visibility};
fn validate_item_fn(sig: &syn::Signature, vis: &syn::Visibility) -> Result<(), syn::Error> {
// Disallow const, unsafe or abi linkage, generics etc
if let Some(c) = &sig.constness {
return Err(Error::new(c.span, "test-harness tests may not be 'const'"));
}
if let Some(u) = &sig.unsafety {
return Err(Error::new(u.span, "test-harness tests may not be 'unsafe'"));
}
if let Some(abi) = &sig.abi {
return Err(Error::new(
abi.extern_token.span,
"test-harness test may not have custom linkage",
));
}
if !sig.generics.params.is_empty() || sig.generics.where_clause.is_some() {
return Err(Error::new(sig.fn_token.span, "test-harness tests may not have generics"));
}
if sig.inputs.len() != 1 {
return Err(Error::new(
sig.paren_token.span,
"test-harness tests take exactly one argument, which must `impl TestHarness`",
));
}
if let Some(dot3) = &sig.variadic {
return Err(Error::new(dot3.dots.spans[0], "test-harness tests may not be variadic"));
}
// Require the target function acknowledge it is async.
if sig.asyncness.is_none() {
return Err(Error::new(sig.ident.span(), "test-harness tests must be declared as 'async'"));
}
// The attributes defined in this crate purposefully mangle the names and visibility of the fns
// to which they are applied. As such, they should not be applied to `pub` fns, which would
// indicate that the client plans to use them elsewhere.
if let Some(token_span) = match vis {
Visibility::Public(pub_vis) => Some(pub_vis.pub_token.span),
Visibility::Crate(crate_vis) => Some(crate_vis.crate_token.span),
Visibility::Restricted(restricted_vis) => Some(restricted_vis.pub_token.span),
Visibility::Inherited => None,
} {
return Err(Error::new(token_span, "test-harness tests cannot be called elsewhere, so they must have inherited (i.e. non-pub) visibility"));
}
Ok(())
}
/// Used to run tests that require TestHarness types as inputs on a singlethreaded executor. This
/// attribute should be used instead of `#[test]`, not in addition to it.
///
/// e.g.
///
/// ```
/// impl TestHarness for SomeHarness {..}
///
/// #[test_harness::run_singlethreaded_test]
/// async fn test_foo(harness: SomeHarness) {
/// // use harness
/// }
/// ```
#[proc_macro_attribute]
pub fn run_singlethreaded_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as syn::ItemFn);
let syn::ItemFn { attrs, sig, vis, block } = item;
if let Err(e) = validate_item_fn(&sig, &vis) {
return e.to_compile_error().into();
}
let inputs = sig.inputs;
let span = sig.ident.span();
let ident = sig.ident;
let output = quote_spanned! {span=>
// Preserve any original attributes.
#(#attrs)* #[test]
fn #ident () {
// Note: `ItemFn::block` includes the function body braces. Do not
// add additional braces (will break source code coverage analysis).
// TODO(https://fxbug.dev/42157203): Try to improve the Rust compiler to ease
// this restriction.
async fn func(#inputs) #block
let func = move |_| { ::test_harness::run_with_harness(func) };
::test_harness::run_singlethreaded_test(func)
}
};
output.into()
}