diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e730feb51..5000860cc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,7 +12,7 @@ mod stats; mod version; use std::path::{Path, PathBuf}; -use std::process::{Command, ExitCode}; +use std::process::{Command, ExitCode, Stdio}; const USAGE: &str = "\ usage: cargo xtask @@ -21,8 +21,10 @@ commands: assets check that every icon/image/font/asset is attributed in ASSETS.md layers enforce the crate dependency layering (plan/architecture.md ยง3) pdf-deps verify the pinned PDF backend, including Krilla dependencies - wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates - ci fmt --check, clippy -D warnings, test, assets, layers, wasm (stops at first failure) + wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates (one call) + test workspace tests (cargo nextest when installed, else cargo test), then doctests + ci fmt --check, pdf-deps, clippy -D warnings, test, doctest, assets, layers, wasm + (stops at first failure) corpus [--download] show where test corpora live; --download fetches PngSuite into corpus/pngsuite stats [--exact] count tests and lines per crate (--exact: ask the test harness via `-- --list`) @@ -39,6 +41,7 @@ fn main() -> ExitCode { Some("layers") => cmd_layers(), Some("pdf-deps") => pdf_deps::run(&root()), Some("wasm") => cmd_wasm(), + Some("test") => cmd_test().and_then(|()| cmd_doctest()), Some("ci") => cmd_ci(), Some("corpus") => cmd_corpus(rest.contains(&"--download")), Some("stats") => stats::run(&root(), rest.contains(&"--exact")), @@ -134,21 +137,51 @@ fn wasm_set() -> Result, String> { .collect()) } +/// One `cargo check` for the whole set: each crate is checked once, and +/// `--keep-going` still reports every failing crate. Features unify across the +/// set, so each crate is checked with the union of the features the set enables. fn cmd_wasm() -> Result<(), String> { let set = wasm_set()?; - let mut results = Vec::new(); + if set.is_empty() { + return Err("no wasm-safe crates found in the workspace metadata".into()); + } + let mut c = cargo(); + c.args(["check", "--target", "wasm32-unknown-unknown", "--keep-going"]); for pkg in &set { - let mut c = cargo(); - c.args(["check", "--target", "wasm32-unknown-unknown", "-p", pkg]); - let ok = run(c, &format!("cargo check --target wasm32-unknown-unknown -p {pkg}")).is_ok(); - results.push((pkg.clone(), ok)); + c.args(["-p", pkg]); } - println!("\nwasm32-unknown-unknown check:"); - for (p, ok) in &results { - println!(" {:<6} {p}", if *ok { "ok" } else { "FAIL" }); - } - let failed = results.iter().filter(|r| !r.1).count(); - if failed == 0 { Ok(()) } else { Err(format!("{failed} crate(s) failed the wasm check")) } + run(c, &format!("cargo check --target wasm32-unknown-unknown --keep-going -p {}", set.join(" -p ")))?; + println!("\nwasm32-unknown-unknown check: ok ({} crates: {})", set.len(), set.join(", ")); + Ok(()) +} + +/// Test targets for the test build. Examples are left out so they aren't +/// linked; clippy `--all-targets` still compiles them. +const TEST_TARGETS: [&str; 4] = ["--workspace", "--lib", "--bins", "--tests"]; + +fn nextest_available() -> bool { + cargo().args(["nextest", "--version"]).stdout(Stdio::null()).stderr(Stdio::null()).status().is_ok_and(|s| s.success()) +} + +fn cmd_test() -> Result<(), String> { + let mut c = cargo(); + let tool = if nextest_available() { + c.args(["nextest", "run"]); + "nextest run" + } else { + eprintln!("note: cargo-nextest is not installed, running cargo test (cargo install cargo-nextest --locked)"); + c.arg("test"); + "test" + }; + c.args(TEST_TARGETS); + run(c, &format!("cargo {tool} {}", TEST_TARGETS.join(" "))) +} + +/// nextest does not run doctests. +fn cmd_doctest() -> Result<(), String> { + let mut c = cargo(); + c.args(["test", "--workspace", "--doc"]); + run(c, "cargo test --workspace --doc") } fn cmd_ci() -> Result<(), String> { @@ -171,14 +204,8 @@ fn cmd_ci() -> Result<(), String> { run(c, "cargo clippy --workspace --all-targets -- -D warnings") }), ), - ( - "test", - Box::new(|| { - let mut c = cargo(); - c.args(["test", "--workspace"]); - run(c, "cargo test --workspace") - }), - ), + ("test", Box::new(cmd_test)), + ("doctest", Box::new(cmd_doctest)), ("assets", Box::new(|| assets::run(&root()))), ("layers", Box::new(cmd_layers)), ("wasm", Box::new(cmd_wasm)),