diff --git a/apps/figcraft-cli/tests/it/fonts.rs b/apps/figcraft-cli/tests/it/fonts.rs index 998665c48..a42fd9ce5 100644 --- a/apps/figcraft-cli/tests/it/fonts.rs +++ b/apps/figcraft-cli/tests/it/fonts.rs @@ -2,15 +2,21 @@ //! `--no-system-fonts` or `FIGCRAFT_NO_SYSTEM_FONTS=1` opts out. use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; use serde_json::{Value, json}; /// The result of one `figcraft-cli run` step (script file `name`); `opt_out` sets the environment /// variable. fn run_one(name: &str, step: Value, args: &[&str], opt_out: bool) -> Value { + static NEXT_SCRIPT: AtomicU64 = AtomicU64::new(0); let d = std::env::temp_dir().join(format!("figcraft-cli-fonts-{}", std::process::id())); std::fs::create_dir_all(&d).unwrap(); - let script = d.join(format!("{name}.json")); + // Tests in this binary run concurrently. `sketch_imports_keep_to_no_system_fonts` also calls + // `families`, so a shared `families.json` could be truncated while another child reads it; + // an empty script is valid and makes the CLI exit successfully without printing result JSON. + let sequence = NEXT_SCRIPT.fetch_add(1, Ordering::Relaxed); + let script = d.join(format!("{name}-{sequence}.json")); std::fs::write(&script, json!([step]).to_string()).unwrap(); let mut cmd = Command::new(env!("CARGO_BIN_EXE_figcraft-cli")); cmd.arg("run").arg(&script).args(args); @@ -20,8 +26,21 @@ fn run_one(name: &str, step: Value, args: &[&str], opt_out: bool) -> Value { cmd.env_remove("FIGCRAFT_NO_SYSTEM_FONTS"); } let out = cmd.output().unwrap(); - assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); - let line: Value = serde_json::from_slice(&out.stdout).unwrap(); + assert!( + out.status.success(), + "{name}: CLI status {:?}; stdout={:?}; stderr={}", + out.status.code(), + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr), + ); + let line: Value = serde_json::from_slice(&out.stdout).unwrap_or_else(|error| { + panic!( + "{name} {:?}: CLI returned success without JSON: {error}; stdout={:?}; stderr={}", + args, + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr), + ) + }); line["result"].clone() } diff --git a/crates/text/src/fontdb_tests.rs b/crates/text/src/fontdb_tests.rs index a3dd15396..bf459fe06 100644 --- a/crates/text/src/fontdb_tests.rs +++ b/crates/text/src/fontdb_tests.rs @@ -376,9 +376,19 @@ fn font_bytes_load_into_the_db() { let families = load_font_bytes(f.bytes.to_vec()); assert_eq!(families.len(), 1); assert!(families.iter().all(|f| has_family(f))); - let faces = db().read().unwrap().face_count(); + // The global face count can grow here when another test finishes the concurrent system + // font scan. Count explicitly loaded faces from this family so the idempotency assertion + // stays isolated from unrelated installed fonts. + let loaded_faces = || { + let db = db().read().unwrap(); + families + .iter() + .map(|family| db.faces.iter().filter(|face| face.loaded > 0 && face.family.eq_ignore_ascii_case(family)).count()) + .collect::>() + }; + let faces = loaded_faces(); assert_eq!(load_font_bytes(f.bytes.to_vec()), families); - assert_eq!(db().read().unwrap().face_count(), faces); + assert_eq!(loaded_faces(), faces); } }