Skip to content

Commit

Permalink
Improved testing using a new glob macro
Browse files Browse the repository at this point in the history
  • Loading branch information
jaybosamiya-ms committed Sep 24, 2024
1 parent c18f1a8 commit 3ad16df
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 16 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ insta = { version = "1.30.0" }
similar = { version = "2.2.1" }
glob = "0.3.1"
stacker = "0.1.15"
glob-macro = { path = "glob-macro" }

# Spend more time on initial compilation in exchange for faster runs
[profile.dev.package.insta]
Expand All @@ -53,6 +54,9 @@ opt-level = 3
inherits = "release"
lto = "thin"

[workspace]
members = ["glob-macro"]

# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
Expand Down
14 changes: 14 additions & 0 deletions glob-macro/Cargo.toml
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"] }
15 changes: 15 additions & 0 deletions glob-macro/README.md
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());
}
```
120 changes: 120 additions & 0 deletions glob-macro/src/lib.rs
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)*
}
}
30 changes: 14 additions & 16 deletions tests/snapshot-examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,20 @@ fn pagetable_rs_unchanged() {
check_snapshot(include_str!("../examples/pagetable.rs"));
}

#[glob_macro::glob("./examples/verus-snapshot/**/*.rs")]
#[test]
fn verus_snapshot_unchanged() {
let rustfmt_toml =
std::fs::read_to_string("./examples/verus-snapshot/source/rustfmt.toml").unwrap();
for path in glob::glob("./examples/verus-snapshot/**/*.rs").unwrap() {
let path = path.unwrap();
println!("Checking snapshot for {:?}", path);
check_snapshot_with_config(
&std::fs::read_to_string(path).unwrap(),
verusfmt::RunOptions {
file_name: None,
run_rustfmt: true,
rustfmt_config: verusfmt::RustFmtConfig {
rustfmt_toml: Some(rustfmt_toml.clone()),
},
fn verus_snapshot_unchanged(path: &std::path::Path) {
check_snapshot_with_config(
&std::fs::read_to_string(path).unwrap(),
verusfmt::RunOptions {
file_name: None,
run_rustfmt: true,
rustfmt_config: verusfmt::RustFmtConfig {
rustfmt_toml: Some(
std::fs::read_to_string("./examples/verus-snapshot/source/rustfmt.toml")
.unwrap(),
),
},
);
}
},
);
}

0 comments on commit 3ad16df

Please sign in to comment.