diff --git a/Cargo.lock b/Cargo.lock index c31425a1e..86c4095a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2081,6 +2081,7 @@ dependencies = [ name = "typst-tests" version = "0.2.0" dependencies = [ + "clap 4.2.4", "comemo", "elsa", "iai", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ae4c3acb0..788aade42 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -17,6 +17,7 @@ tiny-skia = "0.6.2" ttf-parser = "0.17" unscanny = "0.1" walkdir = "2" +clap = { version = "4.2.1", features = ["derive"] } [[test]] name = "tests" diff --git a/tests/src/tests.rs b/tests/src/tests.rs index da07c6286..418f6704f 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -2,7 +2,6 @@ use std::cell::{RefCell, RefMut}; use std::collections::HashMap; -use std::env; use std::ffi::OsStr; use std::fs; use std::ops::Range; @@ -33,10 +32,53 @@ const PDF_DIR: &str = "pdf"; const FONT_DIR: &str = "../assets/fonts"; const FILE_DIR: &str = "../assets/files"; -fn main() { - let args = Args::new(env::args().skip(1)); - let mut filtered = Vec::new(); +use clap::Parser; +#[derive(Debug, Clone, Parser)] +#[clap(name = "typst-test", author)] +struct Args { + filter: Vec, + /// runs only the specified subtest + #[arg(short, long)] + subtest: Option, + #[arg(long)] + exact: bool, + #[arg(long)] + update: bool, + #[arg(long)] + pdf: bool, + #[command(flatten)] + print: PrintConfig, + #[arg(long)] + nocapture: bool, // simply ignores the argument +} + +/// Which things to print out for debugging. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Parser)] +struct PrintConfig { + #[arg(long)] + syntax: bool, + #[arg(long)] + model: bool, + #[arg(long)] + frames: bool, +} + +impl Args { + fn matches(&self, path: &Path) -> bool { + if self.exact { + let name = path.file_name().unwrap().to_string_lossy(); + self.filter.iter().any(|v| v == &name) + } else { + let path = path.to_string_lossy(); + self.filter.is_empty() || self.filter.iter().any(|v| path.contains(v)) + } + } +} + +fn main() { + let args = Args::parse(); + let mut filtered = Vec::new(); // Since different tests can affect each other through the memoization // cache, a deterministic order is important for reproducibility. for entry in WalkDir::new("typ").sort_by_file_name() { @@ -78,14 +120,9 @@ fn main() { let pdf_path = args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf")); - ok += test( - &mut world, - &src_path, - &png_path, - &ref_path, - pdf_path.as_deref(), - args.update, - ) as usize; + ok += + test(&mut world, &src_path, &png_path, &ref_path, pdf_path.as_deref(), &args) + as usize; } if len > 1 { @@ -104,66 +141,6 @@ fn main() { } } -/// Parsed command line arguments. -struct Args { - filter: Vec, - exact: bool, - pdf: bool, - update: bool, - print: PrintConfig, -} - -/// Which things to print out for debugging. -#[derive(Default, Copy, Clone, Eq, PartialEq)] -struct PrintConfig { - syntax: bool, - model: bool, - frames: bool, -} - -impl Args { - fn new(args: impl Iterator) -> Self { - let mut filter = Vec::new(); - let mut exact = false; - let mut pdf = false; - let mut update = env::var_os("UPDATE_EXPECT").is_some(); - let mut print = PrintConfig::default(); - - for arg in args { - match arg.as_str() { - // Ignore this, its for cargo. - "--nocapture" => {} - // Match only the exact filename. - "--exact" => exact = true, - // Generate PDFs. - "--pdf" => pdf = true, - // Update the reference images. - "--update" => update = true, - // Debug print the syntax trees. - "--syntax" => print.syntax = true, - // Debug print the model. - "--model" => print.model = true, - // Debug print the frames. - "--frames" => print.frames = true, - // Everything else is a file filter. - _ => filter.push(arg), - } - } - - Self { filter, exact, pdf, update, print } - } - - fn matches(&self, path: &Path) -> bool { - if self.exact { - let name = path.file_name().unwrap().to_string_lossy(); - self.filter.iter().any(|v| v == &name) - } else { - let path = path.to_string_lossy(); - self.filter.is_empty() || self.filter.iter().any(|v| path.contains(v)) - } - } -} - fn library() -> Library { /// Display: Test /// Category: test @@ -357,7 +334,7 @@ fn test( png_path: &Path, ref_path: &Path, pdf_path: Option<&Path>, - update: bool, + args: &Args, ) -> bool { let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path); println!("Testing {}", name.display()); @@ -374,6 +351,12 @@ fn test( let parts: Vec<_> = text.split("\n---").collect(); for (i, &part) in parts.iter().enumerate() { + if let Some(x) = args.subtest { + if x != i { + println!("skipped subtest {i}"); + continue; + } + } let is_header = i == 0 && parts.len() > 1 && part @@ -424,7 +407,7 @@ fn test( .zip(ref_pixmap.data()) .any(|(&a, &b)| a.abs_diff(b) > 2) { - if update { + if args.update { update_image(png_path, ref_path); updated = true; } else { @@ -433,7 +416,7 @@ fn test( } } } else if !document.pages.is_empty() { - if update { + if args.update { update_image(png_path, ref_path); updated = true; } else {