-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improved testing using a new glob macro
- Loading branch information
1 parent
c18f1a8
commit 3ad16df
Showing
6 changed files
with
190 additions
and
16 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "glob-macro" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
glob = "0.3.1" | ||
proc-macro2 = "1.0.86" | ||
quote = "1.0.37" | ||
syn = "2.0.77" | ||
syn-mid = { version = "0.6.0", features = ["clone-impls"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Glob Macro | ||
|
||
A small and simple crate that lets one apply a [`glob`](https://docs.rs/glob/latest/glob/) in a macro position. | ||
|
||
## Usage | ||
|
||
The main intended use case for this is to write tests that run over all files in some directory. For example: | ||
|
||
```rs | ||
#[glob("./path/to/**/*.inp")] | ||
#[test] | ||
fn test(path: &Path) { | ||
assert!(path.exists()); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use proc_macro::TokenStream; | ||
use proc_macro2::{Span, TokenStream as TokenStream2}; | ||
use quote::quote; | ||
use syn::Error; | ||
use syn_mid::ItemFn; | ||
|
||
#[proc_macro_attribute] | ||
pub fn glob(args: TokenStream, function: TokenStream) -> TokenStream { | ||
if args.is_empty() { | ||
return Error::new(Span::call_site(), "#[glob] attribute requires an argument") | ||
.to_compile_error() | ||
.into(); | ||
} | ||
|
||
let glob_path = match syn::parse(args) { | ||
Ok(p) => p, | ||
Err(err) => return err.to_compile_error().into(), | ||
}; | ||
|
||
let function: ItemFn = syn::parse_macro_input!(function); | ||
|
||
if !function | ||
.attrs | ||
.iter() | ||
.any(|attr| attr.path().is_ident("test")) | ||
{ | ||
return Error::new( | ||
Span::call_site(), | ||
"#[glob] attribute currently only supports running on #[test] functions", | ||
) | ||
.to_compile_error() | ||
.into(); | ||
} | ||
|
||
glob2(glob_path, function).into() | ||
} | ||
|
||
fn glob2(glob_path: syn::LitStr, function: ItemFn) -> TokenStream2 { | ||
let paths = match glob::glob(&glob_path.value()) { | ||
Ok(paths) => paths, | ||
Err(err) => { | ||
return Error::new( | ||
glob_path.span(), | ||
format!("#[glob] called with invalid value: {err:?}"), | ||
) | ||
.to_compile_error(); | ||
} | ||
}; | ||
|
||
let paths = match paths.collect::<Result<Vec<_>, _>>() { | ||
Ok(p) => p, | ||
Err(err) => { | ||
return Error::new(glob_path.span(), format!("#[glob] error: {err:?}")) | ||
.to_compile_error(); | ||
} | ||
}; | ||
let counter_width = (paths.len() - 1).to_string().len(); | ||
|
||
let common_ancestor = paths | ||
.iter() | ||
.fold(paths[0].clone(), |ancestor, path| { | ||
ancestor | ||
.components() | ||
.zip(path.components()) | ||
.take_while(|(a, b)| a == b) | ||
.map(|(a, _)| a) | ||
.collect() | ||
}) | ||
.components() | ||
.count(); | ||
|
||
let mut functions = vec![]; | ||
|
||
for (i, path) in paths.iter().enumerate() { | ||
let function_name = syn::Ident::new( | ||
&format!( | ||
"{}__{:0width$}__{}", | ||
function.sig.ident, | ||
i, | ||
path.components() | ||
.skip(common_ancestor) | ||
.map(|p| p.as_os_str().to_string_lossy()) | ||
.collect::<Vec<_>>() | ||
.join("/") | ||
.replace("/", "__") | ||
.replace(".", "_") | ||
.replace("-", "_") | ||
.replace(" ", "_"), | ||
width = counter_width | ||
), | ||
function.sig.ident.span(), | ||
); | ||
|
||
let path_buf = syn::LitStr::new(&path.to_string_lossy(), Span::call_site()); | ||
|
||
let mut inner_function = function.clone(); | ||
inner_function.sig.ident = syn::Ident::new("test", function.sig.ident.span()); | ||
inner_function.attrs = inner_function | ||
.attrs | ||
.into_iter() | ||
.filter(|attr| !attr.path().is_ident("test")) | ||
.collect(); | ||
|
||
let function = quote! { | ||
#[test] | ||
#[allow(non_snake_case)] | ||
fn #function_name() { | ||
#inner_function | ||
let path: &str = #path_buf; | ||
test(std::path::Path::new(&path)); | ||
} | ||
}; | ||
|
||
functions.push(function); | ||
} | ||
|
||
quote! { | ||
#(#functions)* | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters