mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-19 02:10:25 -05:00
feat(testkit): #1037 tests record why they skip
libtest has no skipped status and hides the output of passing tests, so a test that returns early without a GPU, a corpus file, a craft-fonts face or a model passed silently. `lightcraft-testkit` (the testkit slot the layering table already reserves, dev-dependency only, std only) adds `skip!(Need, "reason")`: it prints the note as before and, when LIGHTCRAFT_SKIP_LOG names a file, appends one JSON line (need, reason, test, binary, file:line). Plain `cargo test` is unchanged. The 61 existing skip sites use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
76b5377868
commit
3e42f2e05c
34 files changed
+297
-61
No files matched your search
Generated
+12
@@ -3081,6 +3081,7 @@ dependencies = [
|
||||
"lightcraft-engine",
|
||||
"lightcraft-raster",
|
||||
"lightcraft-sysmem",
|
||||
"lightcraft-testkit",
|
||||
"lightcraft-ui-egui",
|
||||
"log",
|
||||
"muda",
|
||||
@@ -3144,6 +3145,7 @@ dependencies = [
|
||||
"lightcraft-geom",
|
||||
"lightcraft-heif",
|
||||
"lightcraft-raster",
|
||||
"lightcraft-testkit",
|
||||
"moxcms 0.9.1",
|
||||
"png",
|
||||
"proptest",
|
||||
@@ -3239,6 +3241,7 @@ dependencies = [
|
||||
"lightcraft-raw",
|
||||
"lightcraft-scenes",
|
||||
"lightcraft-segment",
|
||||
"lightcraft-testkit",
|
||||
"lightcraft-tiff",
|
||||
"log",
|
||||
"miniz_oxide 0.8.9",
|
||||
@@ -3254,6 +3257,7 @@ dependencies = [
|
||||
name = "lightcraft-faces"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"lightcraft-testkit",
|
||||
"matrixmultiply",
|
||||
"ndarray",
|
||||
"proptest",
|
||||
@@ -3304,6 +3308,7 @@ dependencies = [
|
||||
"lightcraft-raster",
|
||||
"lightcraft-raw",
|
||||
"lightcraft-scenes",
|
||||
"lightcraft-testkit",
|
||||
"log",
|
||||
"pollster 1.0.1",
|
||||
"wgpu",
|
||||
@@ -3424,6 +3429,7 @@ dependencies = [
|
||||
"lightcraft-geom",
|
||||
"lightcraft-meta",
|
||||
"lightcraft-raster",
|
||||
"lightcraft-testkit",
|
||||
"lightcraft-tiff",
|
||||
"miniz_oxide 0.8.9",
|
||||
"proptest",
|
||||
@@ -3454,6 +3460,7 @@ dependencies = [
|
||||
"candle-core",
|
||||
"candle-nn",
|
||||
"lightcraft-fetch",
|
||||
"lightcraft-testkit",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3464,6 +3471,10 @@ dependencies = [
|
||||
name = "lightcraft-sysmem"
|
||||
version = "0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-testkit"
|
||||
version = "0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-tiff"
|
||||
version = "0.6.0"
|
||||
@@ -3493,6 +3504,7 @@ dependencies = [
|
||||
"lightcraft-pipeline",
|
||||
"lightcraft-preview",
|
||||
"lightcraft-raster",
|
||||
"lightcraft-testkit",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -39,6 +39,7 @@ lightcraft-fetch = { path = "crates/fetch" }
|
||||
lightcraft-heif = { path = "crates/heif" }
|
||||
lightcraft-segment = { path = "crates/segment" }
|
||||
lightcraft-geocode = { path = "crates/geocode" }
|
||||
lightcraft-testkit = { path = "crates/testkit" }
|
||||
|
||||
# foundation
|
||||
thiserror = "2"
|
||||
|
||||
@@ -48,3 +48,7 @@ muda = { workspace = true }
|
||||
objc2 = { workspace = true }
|
||||
objc2-app-kit = { workspace = true }
|
||||
objc2-foundation = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
@@ -278,11 +278,11 @@ mod tests {
|
||||
..wgpu::InstanceDescriptor::new_without_display_handle()
|
||||
});
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) else {
|
||||
eprintln!("no GPU adapter: skipped");
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter");
|
||||
return;
|
||||
};
|
||||
let Ok((device, _queue)) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) else {
|
||||
eprintln!("no GPU device: skipped");
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU device");
|
||||
return;
|
||||
};
|
||||
install_error_handler(&device);
|
||||
|
||||
@@ -48,6 +48,8 @@ heif = ["dep:lightcraft-heif"]
|
||||
avif = ["dep:ravif", "dep:rav1e", "dep:avif-serialize"]
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
# JPEG XL tests read encoded samples without our colour conversion
|
||||
jxl-oxide = { workspace = true }
|
||||
proptest = { workspace = true }
|
||||
|
||||
@@ -31,7 +31,7 @@ fn files(dir: &Path) -> Vec<PathBuf> {
|
||||
fn corpus_images_decode() {
|
||||
let dir = corpus_root().join("images");
|
||||
if !dir.is_dir() {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
}
|
||||
let (mut ok, mut unsupported) = (0, 0);
|
||||
@@ -57,7 +57,7 @@ fn corpus_images_decode() {
|
||||
fn corpus_pngsuite() {
|
||||
let dir = corpus_root().join("pngsuite");
|
||||
if !dir.is_dir() {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
}
|
||||
let mut n = 0;
|
||||
@@ -83,7 +83,7 @@ fn corpus_pngsuite() {
|
||||
fn corpus_raw_is_routed() {
|
||||
let dir = corpus_root().join("raw");
|
||||
if !dir.is_dir() {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
}
|
||||
for p in files(&dir) {
|
||||
@@ -103,7 +103,7 @@ fn system_icc_profiles() {
|
||||
use lightcraft_codecs::icc::{IccColorModel, IccKind, parse};
|
||||
let dir = Path::new("/System/Library/ColorSync/Profiles");
|
||||
if !dir.is_dir() {
|
||||
eprintln!("skip: no system ICC profiles");
|
||||
lightcraft_testkit::skip!(System, "no system ICC profiles");
|
||||
return;
|
||||
}
|
||||
let expect = [
|
||||
|
||||
@@ -51,6 +51,8 @@ denoise = ["dep:lightcraft-denoise", "lightcraft-denoise/runtime", "lightcraft-g
|
||||
rawnind-model = ["denoise", "lightcraft-denoise/rawnind-model", "lightcraft-denoise-core/rawnind-model"]
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
# HDR JPEG XL tests read the file's colour encoding
|
||||
jxl-oxide = { workspace = true }
|
||||
# Lossless synthetic HEIC files (the import test; decoded when the codecs' `heif` feature is on).
|
||||
|
||||
@@ -1828,7 +1828,7 @@ mod tests {
|
||||
.join("raw");
|
||||
for name in ["arw-sony-a7m4-14bit.arw", "cr2-canon-5d3.cr2", "nef-nikon-d5100-lossless.nef", "cr2-canon-7d.cr2", "dng-canon-5d3-16bit.dng"] {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let mut raw = lightcraft_raw::decode(&bytes).unwrap();
|
||||
@@ -1929,13 +1929,13 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw/cr3-canon-r100-raw.cr3");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let raw = match lightcraft_raw::decode(&bytes) {
|
||||
Ok(raw) => raw,
|
||||
Err(lightcraft_raw::RawError::Unsupported(why)) => {
|
||||
eprintln!("skip CR3 sensor decoder: {why}");
|
||||
lightcraft_testkit::skip!(Feature, "CR3 sensor decoder: {why}");
|
||||
return;
|
||||
}
|
||||
Err(error) => panic!("CR3 corpus failed to decode: {error}"),
|
||||
@@ -1964,7 +1964,7 @@ mod tests {
|
||||
let bytes = match std::fs::read(&path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
}
|
||||
Err(e) => panic!("{}: {e}", path.display()),
|
||||
@@ -1995,7 +1995,7 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw/nef-nikon-d7500-lossless14.nef");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let (_, info) = crate::files::load_bytes(&bytes, 400).unwrap();
|
||||
@@ -2015,7 +2015,7 @@ mod tests {
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
@@ -2036,7 +2036,7 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw/rw2-panasonic-fz1000m2-4x3.rw2");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let (img, info) = crate::files::load_bytes(&bytes, 400).unwrap();
|
||||
@@ -2055,7 +2055,7 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw/arw-sony-a7rm2-12bit-uncompressed.arw");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let (_, info) = crate::files::load_bytes(&bytes, 400).unwrap();
|
||||
@@ -2213,7 +2213,7 @@ mod tests {
|
||||
let bytes = match std::fs::read(&path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{}: {e}", path.display()),
|
||||
|
||||
@@ -494,7 +494,7 @@ mod tests {
|
||||
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"));
|
||||
let f = root.join("raw/rw2-panasonic-gx80.rw2");
|
||||
if !f.exists() {
|
||||
eprintln!("skip: {} absent", f.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", f.display());
|
||||
return;
|
||||
}
|
||||
let mut s = Session::new().with_fs();
|
||||
|
||||
@@ -2414,7 +2414,7 @@ mod tests {
|
||||
fn biz_ud_vertical_watermarks_place_punctuation_at_the_top_right() {
|
||||
use ab_glyph::Font;
|
||||
let Some(entry) = crate::fonts::CRAFT_FONTS.iter().find(|f| f.family == "BIZ UDMincho") else {
|
||||
eprintln!("skipped: built without BIZ UDMincho from craft-fonts");
|
||||
lightcraft_testkit::skip!(Fonts, "built without BIZ UDMincho from craft-fonts");
|
||||
return;
|
||||
};
|
||||
let font = ab_glyph::FontRef::try_from_slice(entry.bytes).unwrap();
|
||||
@@ -2486,7 +2486,7 @@ mod tests {
|
||||
compare(composed, decomposed, crate::fonts::CRAFT_FONTS);
|
||||
}
|
||||
} else {
|
||||
eprintln!("skipped Japanese comparisons: built without BIZ UDMincho from craft-fonts");
|
||||
lightcraft_testkit::skip!(Fonts, "Japanese comparisons: built without BIZ UDMincho from craft-fonts");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2510,7 +2510,7 @@ mod tests {
|
||||
use ab_glyph::Font;
|
||||
let fonts = watermark_fonts(crate::fonts::CRAFT_FONTS);
|
||||
if fonts.len() < 2 {
|
||||
eprintln!("skipped: built without CRAFT_FONTS_DIR, so there is no Japanese watermark face");
|
||||
lightcraft_testkit::skip!(Fonts, "built without CRAFT_FONTS_DIR, so there is no Japanese watermark face");
|
||||
return;
|
||||
}
|
||||
for c in "日本語の文字".chars() {
|
||||
@@ -3090,7 +3090,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_batch_reports_a_gpu_fallback_on_the_file_it_hit() {
|
||||
if !lightcraft_gpu::available() {
|
||||
eprintln!("skipped: no GPU adapter ({:?})", lightcraft_gpu::unavailable_reason());
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter ({})", lightcraft_gpu::unavailable_reason().unwrap_or_default());
|
||||
return;
|
||||
}
|
||||
let mut s = crate::Session::with_demo();
|
||||
|
||||
@@ -1133,7 +1133,7 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw");
|
||||
let Ok(gh1) = std::fs::read(dir.join("rw2-panasonic-gh1.rw2")) else {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
};
|
||||
assert_eq!(probe_bytes("gh1.rw2", &gh1).unwrap().embedded_lens, None, "correction off: nothing to apply");
|
||||
@@ -1484,7 +1484,7 @@ mod tests {
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw");
|
||||
let Ok(bytes) = std::fs::read(dir.join("arw-sony-a7rm4-14bit-compressed.arw")) else {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
};
|
||||
let (img, _) = load_bytes(&bytes, 1600).unwrap();
|
||||
|
||||
@@ -179,7 +179,7 @@ fn hostile_ids_unsupported_models_and_odd_folders_are_handled() {
|
||||
/// Settings > Faces downloads; nothing is committed). Without it those tests say so and pass.
|
||||
fn demo_with_yunet() -> Option<Session> {
|
||||
let Some(file) = std::env::var_os("LC_YUNET_MODEL") else {
|
||||
eprintln!("LC_YUNET_MODEL is not set: skipping");
|
||||
lightcraft_testkit::skip!(Model, "LC_YUNET_MODEL is not set");
|
||||
return None;
|
||||
};
|
||||
let dir = temp("yunet");
|
||||
|
||||
@@ -34,7 +34,7 @@ pub(crate) fn setup(d: &std::path::Path, dim: u64) -> (Session, String) {
|
||||
/// committed) and says whether it did: the parts of a test that need to find faces run only then.
|
||||
pub(crate) fn install_yunet(s: &mut Session) -> bool {
|
||||
let Some(file) = std::env::var_os("LC_YUNET_MODEL") else {
|
||||
eprintln!("LC_YUNET_MODEL is not set: skipping the face-finding part");
|
||||
lightcraft_testkit::skip!(Model, "LC_YUNET_MODEL is not set, so the face-finding part is not checked");
|
||||
return false;
|
||||
};
|
||||
let r = s.execute("faces.models.install", &json!({"path": file.to_string_lossy(), "acknowledged": true}));
|
||||
|
||||
@@ -581,7 +581,7 @@ fn convert_raw_to_dng() {
|
||||
let r = s.execute("photo.convertToDng", &json!({})).unwrap();
|
||||
assert_eq!((r["converted"].as_array().unwrap().len(), r["skipped"].as_array().unwrap().len()), (0, 1));
|
||||
if !corpus.exists() {
|
||||
eprintln!("skip: {} absent", corpus.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", corpus.display());
|
||||
return;
|
||||
}
|
||||
let nef = dir.join("shot.nef");
|
||||
@@ -843,7 +843,7 @@ fn dng_export_compression_choices() {
|
||||
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
|
||||
.join("raw/nef-nikon-d5100-uncompressed.nef");
|
||||
if !corpus.exists() {
|
||||
eprintln!("skip: {} absent", corpus.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", corpus.display());
|
||||
return;
|
||||
}
|
||||
let mut s = Session::new().with_fs();
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::time::Instant;
|
||||
#[ignore = "needs LC_PREVIEW_RAW pointing at a real raw file"]
|
||||
fn a_preview_reads_only_the_header_and_the_preview() {
|
||||
let Ok(path) = std::env::var("LC_PREVIEW_RAW") else {
|
||||
eprintln!("skipped: set LC_PREVIEW_RAW=<path to a raw file>");
|
||||
lightcraft_testkit::skip!(Input, "set LC_PREVIEW_RAW=<path to a raw file>");
|
||||
return;
|
||||
};
|
||||
let file_len = std::fs::metadata(&path).expect("the file exists").len();
|
||||
|
||||
@@ -22,4 +22,6 @@ sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
proptest = { workspace = true }
|
||||
@@ -263,7 +263,7 @@ mod tests {
|
||||
|
||||
fn detector() -> Option<Detector> {
|
||||
let Some(bytes) = model() else {
|
||||
eprintln!("LC_YUNET_MODEL is not set: skipping");
|
||||
lightcraft_testkit::skip!(Model, "LC_YUNET_MODEL is not set");
|
||||
return None;
|
||||
};
|
||||
Some(Detector::new(&bytes).unwrap())
|
||||
|
||||
@@ -29,5 +29,7 @@ default = []
|
||||
denoise = ["dep:lightcraft-denoise", "lightcraft-denoise/runtime"]
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
lightcraft-scenes = { workspace = true }
|
||||
lightcraft-raw = { workspace = true }
|
||||
@@ -907,7 +907,7 @@ mod tests {
|
||||
match runner(net, tile) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
eprintln!("skipped: no GPU runner here ({e})");
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU runner here ({e})");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const MAX_LSB: u8 = 3;
|
||||
fn gpu() -> bool {
|
||||
let ok = lightcraft_gpu::available();
|
||||
if !ok {
|
||||
eprintln!("skipped: no GPU adapter");
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter");
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ fn masked(s: &mut DevelopSettings, k: usize) {
|
||||
#[test]
|
||||
fn failed_gpu_renders_fall_back_with_a_reason() {
|
||||
if !lightcraft_gpu::available() {
|
||||
eprintln!("skipped: no GPU adapter ({:?})", lightcraft_gpu::unavailable_reason());
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter ({})", lightcraft_gpu::unavailable_reason().unwrap_or_default());
|
||||
return;
|
||||
}
|
||||
assert_eq!(lightcraft_gpu::unavailable_reason(), None);
|
||||
|
||||
@@ -9,7 +9,7 @@ use lightcraft_pipeline::{RenderRequest, SourceInfo, StageCache};
|
||||
#[test]
|
||||
fn stages_are_counted_and_the_pool_is_bounded_and_trimmed() {
|
||||
if !lightcraft_gpu::available() {
|
||||
eprintln!("skipped: no GPU adapter");
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter");
|
||||
return;
|
||||
}
|
||||
let limit = 64u64 << 20;
|
||||
|
||||
@@ -21,7 +21,8 @@ fn nothing_renders_on_the_gpu_once_the_process_is_ending() {
|
||||
let req = RenderRequest::fit(w, h);
|
||||
let has_gpu = lightcraft_gpu::available();
|
||||
if !has_gpu {
|
||||
eprintln!("no GPU adapter ({:?}): only the CPU side is checked", lightcraft_gpu::unavailable_reason());
|
||||
let why = lightcraft_gpu::unavailable_reason().unwrap_or_default();
|
||||
lightcraft_testkit::skip!(Gpu, "no GPU adapter ({why}), so only the CPU side is checked");
|
||||
}
|
||||
assert!(!lightcraft_gpu::shutting_down());
|
||||
assert!(lightcraft_gpu::wait_idle(Duration::ZERO), "nothing is in flight yet");
|
||||
|
||||
@@ -28,6 +28,8 @@ default = ["jxl"]
|
||||
jxl = ["dep:jxl-oxide"]
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
jpeg-encoder = { workspace = true }
|
||||
proptest = { workspace = true }
|
||||
zune-jpegxl = { workspace = true }
|
||||
|
||||
+21
-21
@@ -25,7 +25,7 @@ const KNOWN_UNSUPPORTED: &[&str] = &[
|
||||
fn corpus_raw_decodes() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let Ok(rd) = std::fs::read_dir(&dir) else {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
};
|
||||
let mut paths: Vec<PathBuf> = rd.flatten().map(|e| e.path()).filter(|p| p.is_file()).collect();
|
||||
@@ -159,7 +159,7 @@ fn corpus_cr2_cfa_patterns() {
|
||||
let mut seen = 0;
|
||||
for (name, want) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -189,7 +189,7 @@ fn corpus_cr2_as_shot_white_balance() {
|
||||
"cr2-canon-80d.cr2",
|
||||
] {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let wb = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}")).wb_multipliers.unwrap_or_else(|| panic!("{name}: no as-shot WB"));
|
||||
@@ -206,7 +206,7 @@ fn corpus_nef_compressed_matches_uncompressed() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let (Ok(a), Ok(b)) = (std::fs::read(dir.join("nef-nikon-d5100-lossless.nef")), std::fs::read(dir.join("nef-nikon-d5100-uncompressed.nef")))
|
||||
else {
|
||||
eprintln!("skip: D5100 NEF pair absent");
|
||||
lightcraft_testkit::skip!(Corpus, "D5100 NEF pair absent");
|
||||
return;
|
||||
};
|
||||
let (a, b) = (decode(&a).unwrap(), decode(&b).unwrap());
|
||||
@@ -231,7 +231,7 @@ fn corpus_nef_compressed_matches_uncompressed() {
|
||||
fn corpus_nef_high_efficiency_is_named() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let (Ok(he), Ok(lossless)) = (std::fs::read(dir.join("nef-nikon-zf-he.nef")), std::fs::read(dir.join("nef-nikon-zf-lossless.nef"))) else {
|
||||
eprintln!("skip: Z f samples absent");
|
||||
lightcraft_testkit::skip!(Corpus, "Z f samples absent");
|
||||
return;
|
||||
};
|
||||
for result in [decode(&he).map(|_| ()), probe_info(&he).map(|_| ())] {
|
||||
@@ -248,7 +248,7 @@ fn corpus_nef_12_bit_black_level_matches_14_bit() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let (Ok(a), Ok(b)) = (std::fs::read(dir.join("nef-nikon-d7500-lossless12.nef")), std::fs::read(dir.join("nef-nikon-d7500-lossless14.nef")))
|
||||
else {
|
||||
eprintln!("skip: D7500 NEF pair absent");
|
||||
lightcraft_testkit::skip!(Corpus, "D7500 NEF pair absent");
|
||||
return;
|
||||
};
|
||||
let (a, b) = (decode(&a).unwrap(), decode(&b).unwrap());
|
||||
@@ -278,7 +278,7 @@ fn corpus_nef_12_bit_black_level_matches_14_bit() {
|
||||
fn corpus_dngs_carry_profile_looks() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let Ok(rd) = std::fs::read_dir(&dir) else {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
};
|
||||
let mut seen = 0;
|
||||
@@ -327,7 +327,7 @@ fn corpus_dngs_carry_profile_looks() {
|
||||
fn corpus_proraw_sky_matte() {
|
||||
let path = corpus_root().join("raw/dng-apple-iphone12pro-proraw.dng");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let t0 = Instant::now();
|
||||
@@ -365,7 +365,7 @@ fn corpus_sony_pre2017_colour_metadata() {
|
||||
let mut seen = 0;
|
||||
for (name, black, [r, b], (cw, ch)) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -397,7 +397,7 @@ fn corpus_sony_a7cr_codings() {
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
@@ -464,7 +464,7 @@ fn corpus_sony_a7cm2_codings() {
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
@@ -509,7 +509,7 @@ fn corpus_sony_black_levels_sit_at_the_data_floor() {
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
@@ -567,7 +567,7 @@ fn corpus_sony_sr2_white_balance() {
|
||||
let mut seen = 0;
|
||||
for (name, [r, b]) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -585,7 +585,7 @@ fn corpus_sony_sr2_white_balance() {
|
||||
fn corpus_sony_a900_packed12() {
|
||||
let name = "arw-sony-a900-packed12.arw";
|
||||
let Ok(bytes) = std::fs::read(corpus_root().join("raw").join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
return;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -607,7 +607,7 @@ fn corpus_sony_16x9_crops() {
|
||||
let bytes = match std::fs::read(dir.join(name)) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
@@ -727,7 +727,7 @@ fn corpus_panasonic_encodings() {
|
||||
let mut seen = 0;
|
||||
for (name, bits, black, (cw, ch), cfa) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -777,7 +777,7 @@ fn corpus_orf_cfa_patterns() {
|
||||
let mut seen = 0;
|
||||
for (name, cfa) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -812,7 +812,7 @@ fn corpus_samsung_srw() {
|
||||
let mut seen = 0;
|
||||
for (name, bits, active, crop, cfa, whole) in cases {
|
||||
let Ok(bytes) = std::fs::read(dir.join(name)) else {
|
||||
eprintln!("skip: {name} absent");
|
||||
lightcraft_testkit::skip!(Corpus, "{name} absent");
|
||||
continue;
|
||||
};
|
||||
let img = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -941,7 +941,7 @@ fn corpus_sony_arw6_samples() {
|
||||
fn corpus_raws_keep_their_container_and_are_not_thumbnail_shells() {
|
||||
let dir = corpus_root().join("raw");
|
||||
let Ok(rd) = std::fs::read_dir(&dir) else {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
};
|
||||
let mut checked = 0;
|
||||
@@ -1004,7 +1004,7 @@ fn sony_embedded_distortion_is_limited_to_validated_models() {
|
||||
] {
|
||||
let path = root.join(name);
|
||||
if !path.exists() {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
continue;
|
||||
}
|
||||
let bytes = std::fs::read(&path).unwrap();
|
||||
@@ -1026,7 +1026,7 @@ fn corpus_kodak_dcs520c() {
|
||||
let name = "2573_Kodak - DCS520C - 12bit (3_2).TIF";
|
||||
let candidates = [corpus_root().join("raw").join("kodak-dcs520c.tif"), Path::new("P:/raw-pixls/Kodak/DCS520C").join(name)];
|
||||
let Some(bytes) = candidates.iter().find_map(|p| std::fs::read(p).ok()) else {
|
||||
eprintln!("skip: DCS520C sample absent");
|
||||
lightcraft_testkit::skip!(Corpus, "DCS520C sample absent");
|
||||
return;
|
||||
};
|
||||
assert_eq!(probe(&bytes), Some(RawFormat::KodakDcs));
|
||||
|
||||
@@ -22,7 +22,7 @@ fn check(name: &str, hash: u64, sensor: (usize, usize), developed: (usize, usize
|
||||
fn check_against(name: &str, hash: u64, sensor: (usize, usize), developed: Option<(usize, usize)>, reference: &str) {
|
||||
let path = corpus().join("raw").join(name);
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let raw = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
@@ -118,7 +118,7 @@ fn canon_m50_craw_crop_is_the_shot_aspect() {
|
||||
// which is (12, 12) inside the valid sensor area.
|
||||
let path = corpus().join("raw").join("cr3-canon-m50-craw.cr3");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", path.display());
|
||||
return;
|
||||
};
|
||||
let raw = decode(&bytes).unwrap();
|
||||
|
||||
@@ -58,7 +58,7 @@ fn partial_reads_describe_raws_as_whole_files_do() {
|
||||
.filter(|p| p.is_file() && !p.extension().is_some_and(|e| e == "part" || e == "txt" || e == "md"))
|
||||
.collect();
|
||||
if paths.is_empty() {
|
||||
eprintln!("skip: {} absent", dir.display());
|
||||
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
|
||||
return;
|
||||
}
|
||||
paths.sort();
|
||||
|
||||
@@ -23,3 +23,7 @@ lightcraft-fetch = { workspace = true }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
candle-core = { workspace = true, features = ["metal"] }
|
||||
candle-nn = { workspace = true, features = ["metal"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
@@ -38,7 +38,7 @@ fn mask_disagreement(a: &[f32], b: &[f32]) -> f32 {
|
||||
#[test]
|
||||
fn matches_reference() {
|
||||
let Some((dir, r)) = setup() else {
|
||||
eprintln!("skipped: set LIGHTCRAFT_SAM3_DIR and LIGHTCRAFT_SAM3_REF");
|
||||
lightcraft_testkit::skip!(Model, "set LIGHTCRAFT_SAM3_DIR and LIGHTCRAFT_SAM3_REF");
|
||||
return;
|
||||
};
|
||||
let mut model = Sam3::load(&dir).unwrap();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "lightcraft-testkit"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "LightCraft test tooling (dev-dependency only): a test that can't run here records why, so `cargo xtask ci` reports it"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Test tooling shared by the workspace's tests. A dev-dependency only: `cargo xtask layers`
|
||||
//! refuses it as a normal dependency. Std only, so every crate can use it at no build cost.
|
||||
//!
|
||||
//! **Skips.** libtest has no "skipped" status and hides the output of passing tests, so a test that
|
||||
//! needs a GPU, a corpus file, the craft-fonts faces or a model, and returns early without them,
|
||||
//! passes silently: "all steps passed" could mean the GPU tests never ran. Such a test calls
|
||||
//! [`skip!`] before it returns:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # let gpu_available = false;
|
||||
//! if !gpu_available {
|
||||
//! lightcraft_testkit::skip!(Gpu, "no GPU adapter");
|
||||
//! return;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! It prints the note to stderr as before and, when the [`SKIP_LOG_ENV`] variable names a file
|
||||
//! (`cargo xtask ci` sets it), appends one JSON line there:
|
||||
//! `{"need":"gpu","reason":"no GPU adapter","test":"camera_tone","binary":"equivalence","location":"crates/gpu/tests/equivalence.rs:22"}`.
|
||||
//! `cargo xtask ci` lists the skips by need and reason in its summary and in
|
||||
//! `target/ci/report.json`. A test that runs part of its checks (e.g. only the CPU half) records
|
||||
//! the part it could not run the same way.
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Write as _;
|
||||
use std::path::Path;
|
||||
|
||||
/// The environment variable naming the file skips are appended to (JSON lines).
|
||||
pub const SKIP_LOG_ENV: &str = "LIGHTCRAFT_SKIP_LOG";
|
||||
|
||||
/// What a skipped test needed and didn't find.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Need {
|
||||
/// A usable GPU adapter (`lightcraft_gpu::available()`).
|
||||
Gpu,
|
||||
/// A file from the gitignored `corpus/` (`cargo xtask corpus --download`) or a private corpus.
|
||||
Corpus,
|
||||
/// Faces from craft-fonts, embedded only in builds with `CRAFT_FONTS_DIR`.
|
||||
Fonts,
|
||||
/// A model file named by an environment variable (SAM 3, YuNet, …).
|
||||
Model,
|
||||
/// An input file named by an environment variable (opt-in tests).
|
||||
Input,
|
||||
/// Something the operating system provides (system ICC profiles, …).
|
||||
System,
|
||||
/// Support this build doesn't have yet (a decoder that refuses a file variant, an optional
|
||||
/// cargo feature that is off).
|
||||
Feature,
|
||||
}
|
||||
|
||||
impl Need {
|
||||
/// The name in the skip log and the CI summary.
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Need::Gpu => "gpu",
|
||||
Need::Corpus => "corpus",
|
||||
Need::Fonts => "fonts",
|
||||
Need::Model => "model",
|
||||
Need::Input => "input",
|
||||
Need::System => "system",
|
||||
Need::Feature => "feature",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Record that this test (or part of it) can't run here: `skip!(Gpu, "no GPU adapter ({why})")`.
|
||||
/// The first argument names a [`Need`] variant; the rest is a `format!` message. Doesn't return
|
||||
/// from the test: follow it with `return` (or `continue`) as the test needs.
|
||||
#[macro_export]
|
||||
macro_rules! skip {
|
||||
($need:ident, $($arg:tt)+) => {
|
||||
$crate::record($crate::Need::$need, &::std::format!($($arg)+), ::std::file!(), ::std::line!())
|
||||
};
|
||||
}
|
||||
|
||||
/// What [`skip!`] calls: print the note and append it to the skip log, if one is set. Never fails
|
||||
/// the test: a log that can't be written loses the entry, not the run.
|
||||
pub fn record(need: Need, reason: &str, file: &str, line: u32) {
|
||||
eprintln!("skipped ({}): {reason}", need.as_str());
|
||||
let log = std::env::var_os(SKIP_LOG_ENV).filter(|p| !p.is_empty());
|
||||
if let Some(log) = log {
|
||||
let thread = std::thread::current();
|
||||
let binary = std::env::current_exe().ok().and_then(|p| p.file_stem().map(|s| binary_name(&s.to_string_lossy()).to_owned()));
|
||||
let entry = entry(need, reason, thread.name().unwrap_or(""), binary.as_deref().unwrap_or(""), file, line);
|
||||
append(Path::new(&log), &entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Append one line in a single write, so lines from parallel tests don't interleave.
|
||||
fn append(path: &Path, line: &str) {
|
||||
let file = std::fs::OpenOptions::new().create(true).append(true).open(path);
|
||||
if let Err(e) = file.and_then(|mut f| f.write_all(line.as_bytes())) {
|
||||
eprintln!("{SKIP_LOG_ENV}: can't append to {}: {e}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
/// A test binary's name without the hash cargo appends (`equivalence-0123abcd` → `equivalence`).
|
||||
fn binary_name(stem: &str) -> &str {
|
||||
match stem.rsplit_once('-') {
|
||||
Some((name, hash)) if !name.is_empty() && hash.len() == 16 && hash.bytes().all(|b| b.is_ascii_hexdigit()) => name,
|
||||
_ => stem,
|
||||
}
|
||||
}
|
||||
|
||||
/// One skip-log line (JSON, newline-terminated).
|
||||
fn entry(need: Need, reason: &str, test: &str, binary: &str, file: &str, line: u32) -> String {
|
||||
let mut s = String::from("{");
|
||||
for (i, (key, value)) in [("need", need.as_str()), ("reason", reason), ("test", test), ("binary", binary)].into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
s.push(',');
|
||||
}
|
||||
let _ = write!(s, "\"{key}\":\"{}\"", escape(value));
|
||||
}
|
||||
let _ = writeln!(s, ",\"location\":\"{}:{line}\"}}", escape(&file.replace('\\', "/")));
|
||||
s
|
||||
}
|
||||
|
||||
/// A JSON string's contents.
|
||||
fn escape(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'"' => out.push_str("\\\""),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if u32::from(c) < 0x20 => {
|
||||
let _ = write!(out, "\\u{:04x}", u32::from(c));
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn an_entry_is_one_json_line_with_its_text_escaped() {
|
||||
let line = entry(Need::Corpus, "skip: C:\\corpus\\raw \"absent\"\n\u{1}", "corpus::decodes", "corpus", "crates\\raw\\tests\\corpus.rs", 28);
|
||||
assert!(line.ends_with('\n'));
|
||||
assert_eq!(line.matches('\n').count(), 1, "{line}");
|
||||
assert_eq!(
|
||||
line,
|
||||
"{\"need\":\"corpus\",\"reason\":\"skip: C:\\\\corpus\\\\raw \\\"absent\\\"\\n\\u0001\",\"test\":\"corpus::decodes\",\
|
||||
\"binary\":\"corpus\",\"location\":\"crates/raw/tests/corpus.rs:28\"}\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_names_lose_cargos_hash_only() {
|
||||
assert_eq!(binary_name("equivalence-0f3a9c2b1d4e5f60"), "equivalence");
|
||||
assert_eq!(binary_name("lightcraft_engine-0f3a9c2b1d4e5f60"), "lightcraft_engine");
|
||||
assert_eq!(binary_name("sparse_corpus"), "sparse_corpus");
|
||||
assert_eq!(binary_name("cr3-corpus"), "cr3-corpus");
|
||||
assert_eq!(binary_name("-0f3a9c2b1d4e5f60"), "-0f3a9c2b1d4e5f60");
|
||||
assert_eq!(binary_name("tool-cafe"), "tool-cafe");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appended_lines_accumulate() {
|
||||
let dir = std::env::temp_dir().join(format!("lightcraft-testkit-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let log = dir.join("skips.jsonl");
|
||||
let _ = std::fs::remove_file(&log);
|
||||
let threads: Vec<_> = (0..8)
|
||||
.map(|i| {
|
||||
let log = log.clone();
|
||||
std::thread::spawn(move || append(&log, &entry(Need::Gpu, &format!("no GPU {i}"), "t", "b", "f.rs", i)))
|
||||
})
|
||||
.collect();
|
||||
for t in threads {
|
||||
t.join().unwrap();
|
||||
}
|
||||
let text = std::fs::read_to_string(&log).unwrap();
|
||||
assert_eq!(text.lines().count(), 8);
|
||||
assert!(text.lines().all(|l| l.starts_with("{\"need\":\"gpu\"") && l.ends_with('}')));
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_log_directory_does_not_fail_the_test() {
|
||||
append(Path::new("/nonexistent-lightcraft-dir/skips.jsonl"), "{}\n");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ serde_json = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# tests record why they skip (`cargo xtask ci` lists the skips)
|
||||
lightcraft-testkit = { workspace = true }
|
||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
||||
lightcraft-denoise = { workspace = true, features = ["runtime"] }
|
||||
lightcraft-codecs = { workspace = true }
|
||||
|
||||
@@ -597,7 +597,7 @@ mod tests {
|
||||
lightcraft_engine::CRAFT_FONTS.iter().filter(|font| font.serves(language.script())).map(|font| font.family).collect();
|
||||
faces.dedup();
|
||||
if faces.is_empty() {
|
||||
eprintln!("skipped {}: built without a craft-fonts face for {}", language.code(), language.script());
|
||||
lightcraft_testkit::skip!(Fonts, "{}: built without a craft-fonts face for {}", language.code(), language.script());
|
||||
continue;
|
||||
}
|
||||
ctx.fonts_mut(|fonts| {
|
||||
@@ -1200,7 +1200,7 @@ mod tests {
|
||||
let samples = [(Locale::Ja, "日本語の文字"), (Locale::ZhHans, "简体中文字"), (Locale::ZhHant, "繁體中文字")];
|
||||
for (language, sample) in samples {
|
||||
if !lightcraft_engine::CRAFT_FONTS.iter().any(|font| font.serves(language.script())) {
|
||||
eprintln!("skipped {}: built without a craft-fonts face for {}", language.code(), language.script());
|
||||
lightcraft_testkit::skip!(Fonts, "{}: built without a craft-fonts face for {}", language.code(), language.script());
|
||||
continue;
|
||||
}
|
||||
set_language(language);
|
||||
|
||||
@@ -415,7 +415,7 @@ mod tests {
|
||||
#[test]
|
||||
fn thai_face_is_a_fallback_in_every_ui_family_when_craft_fonts_carry_one() {
|
||||
let Some(thai) = lightcraft_engine::CRAFT_FONTS.iter().find(|f| f.covers("Thai")) else {
|
||||
eprintln!("no Thai face in craft-fonts: nothing to check");
|
||||
lightcraft_testkit::skip!(Fonts, "no Thai face in craft-fonts");
|
||||
return;
|
||||
};
|
||||
set_language(Locale::En);
|
||||
|
||||
Reference in new issue
Block a user