nipdf_test_macro/
lib.rs

1#![allow(clippy::unwrap_used)]
2
3use glob::glob;
4use itertools::Itertools;
5use proc_macro::TokenStream;
6use quote::quote;
7use std::{env::var, path::PathBuf};
8
9/// Glob `*.pdf`, `*.pdf.link` files in `sample_files`, `../pdf/`, `pdf.js/test/pdfs` directories,
10/// relative to crate directory.
11/// for each file generate a test, using `#[test-case]` attribute.
12/// For example: `#[test_case("sample_files/normal/foo.pdf")]`
13///
14/// To save compile time, file list cached in `${workspace}/target/render-test.list` file, if file
15/// not exist, it will re-generated by directories. Each line in cache file is a file path.
16///
17/// Using `proc-macro2`, `syn`, `quote` crates to help for parsing and generating code.
18#[proc_macro_attribute]
19pub fn pdf_file_test_cases(_attr: TokenStream, item: TokenStream) -> TokenStream {
20    let dirs = vec![
21        "../nipdf/sample_files",
22        "../../pdf",
23        "../nipdf/pdf.js/test/pdfs",
24    ];
25    let patterns = vec!["**/*.pdf", "**/*.pdf.link"];
26    let files = dirs
27        .into_iter()
28        .cartesian_product(patterns)
29        .flat_map(|(dir, pattern)| {
30            let dir: PathBuf = [&var("CARGO_MANIFEST_DIR").unwrap(), dir, pattern]
31                .iter()
32                .collect();
33            glob(dir.to_str().unwrap())
34                .unwrap()
35                .map(|p| p.unwrap().to_str().unwrap().to_owned())
36        })
37        .collect_vec();
38
39    let mut test_case_attrs = Vec::with_capacity(files.len());
40    for file in files {
41        let test_case_attr = quote! {
42            #[test_case(#file)]
43        };
44        test_case_attrs.push(test_case_attr);
45    }
46
47    let input = syn::parse_macro_input!(item as syn::ItemFn);
48    let tokens = quote! {
49        #(#test_case_attrs)*
50        #input
51    };
52    tokens.into()
53}