use std::fs;
use std::io::Read;
use std::panic::{self, AssertUnwindSafe};
pub fn emulate_macro_expansion_fallible<F>(
mut file: fs::File,
macro_path: &str,
proc_macro_fn: F,
) -> Result<(), Error>
where
F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream,
{
struct MacroVisitor<F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream> {
macro_path: syn::Path,
proc_macro_fn: AssertUnwindSafe<F>,
}
impl<'ast, F> syn::visit::Visit<'ast> for MacroVisitor<F>
where
F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream,
{
fn visit_macro(&mut self, macro_item: &'ast syn::Macro) {
if macro_item.path == self.macro_path {
(*self.proc_macro_fn)(macro_item.tokens.clone());
}
}
}
let proc_macro_fn = AssertUnwindSafe(proc_macro_fn);
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::IoError(e))?;
let ast =
AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?);
let macro_path: syn::Path = syn::parse_str(macro_path).map_err(|e| Error::ParseError(e))?;
panic::catch_unwind(|| {
syn::visit::visit_file(
&mut MacroVisitor::<F> {
macro_path,
proc_macro_fn,
},
&*ast,
);
})
.map_err(|_| {
Error::ParseError(syn::parse::Error::new(
proc_macro2::Span::call_site(),
"macro expansion panicked",
))
})?;
Ok(())
}
fn uses_derive(attrs: &[syn::Attribute], derive_name: &syn::Path) -> Result<bool, Error> {
for attr in attrs {
if attr.path.is_ident("derive") {
let meta = attr.parse_meta().map_err(|e| Error::ParseError(e))?;
if let syn::Meta::List(ml) = meta {
let uses_derive = ml.nested.iter().any(|nested_meta| {
*nested_meta == syn::NestedMeta::Meta(syn::Meta::Path(derive_name.clone()))
});
if uses_derive {
return Ok(true);
}
}
}
}
Ok(false)
}
pub fn emulate_derive_expansion_fallible<F>(
mut file: fs::File,
macro_path: &str,
derive_fn: F,
) -> Result<(), Error>
where
F: Fn(syn::DeriveInput) -> proc_macro2::TokenStream,
{
struct MacroVisitor<F: Fn(syn::DeriveInput) -> proc_macro2::TokenStream> {
macro_path: syn::Path,
derive_fn: AssertUnwindSafe<F>,
}
impl<'ast, F> syn::visit::Visit<'ast> for MacroVisitor<F>
where
F: Fn(syn::DeriveInput) -> proc_macro2::TokenStream,
{
fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
match uses_derive(&node.attrs, &self.macro_path) {
Ok(uses) => {
if uses {
(*self.derive_fn)(node.clone().into());
}
}
Err(e) => panic!(
"Failed expanding derive macro for {:?}: {}",
self.macro_path, e
),
}
}
fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
match uses_derive(&node.attrs, &self.macro_path) {
Ok(uses) => {
if uses {
(*self.derive_fn)(node.clone().into());
}
}
Err(e) => panic!(
"Failed expanding derive macro for {:?}: {}",
self.macro_path, e
),
}
}
}
let derive_fn = AssertUnwindSafe(derive_fn);
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::IoError(e))?;
let ast =
AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?);
let macro_path: syn::Path = syn::parse_str(macro_path).map_err(|e| Error::ParseError(e))?;
panic::catch_unwind(|| {
syn::visit::visit_file(
&mut MacroVisitor::<F> {
macro_path,
derive_fn,
},
&*ast,
);
})
.map_err(|_| {
Error::ParseError(syn::parse::Error::new(
proc_macro2::Span::call_site(),
"macro expansion panicked",
))
})?;
Ok(())
}
#[deprecated]
pub fn emulate_macro_expansion<F>(file: fs::File, macro_path: &str, proc_macro_fn: F)
where
F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream,
{
emulate_macro_expansion_fallible(file, macro_path, proc_macro_fn).unwrap()
}
#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
ParseError(syn::parse::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::IoError(e) => e.fmt(f),
Error::ParseError(e) => e.fmt(f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::IoError(e) => e.source(),
Error::ParseError(e) => e.source(),
}
}
}
#[cfg(test)]
mod tests {
extern crate cargo_tarpaulin;
use self::cargo_tarpaulin::config::Config;
use self::cargo_tarpaulin::launch_tarpaulin;
use std::{env, time};
#[test]
fn proc_macro_coverage() {
let mut config = Config::default();
let test_dir = env::current_dir()
.unwrap()
.join("examples")
.join("custom_assert");
config.manifest = test_dir.join("Cargo.toml");
config.test_timeout = time::Duration::from_secs(60);
let (_trace_map, return_code) = launch_tarpaulin(&config, &None).unwrap();
assert_eq!(return_code, 0);
}
#[test]
fn derive_macro_coverage() {
let mut config = Config::default();
let test_dir = env::current_dir()
.unwrap()
.join("examples")
.join("custom_derive");
config.manifest = test_dir.join("Cargo.toml");
config.test_timeout = time::Duration::from_secs(60);
let (_trace_map, return_code) = launch_tarpaulin(&config, &None).unwrap();
assert_eq!(return_code, 0);
}
}